chanmin0723 commited on
Commit
f27c169
Β·
verified Β·
1 Parent(s): 097054e

upload index.html

Browse files
Files changed (2) hide show
  1. README.md +45 -4
  2. index.html +773 -18
README.md CHANGED
@@ -1,10 +1,51 @@
1
  ---
2
  title: Block Blast Solver
3
- emoji: πŸŒ–
4
- colorFrom: purple
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Block Blast Solver
3
+ emoji: 🧩
4
+ colorFrom: green
5
+ colorTo: indigo
6
  sdk: static
7
  pinned: false
8
  ---
9
 
10
+ # Block Blast Solver
11
+
12
+ λ³΄λ“œ μƒνƒœμ™€ 3개 쑰각 λͺ¨μ–‘을 그리면, 완전탐색(λ°±νŠΈλž˜ν‚Ή)으둜 3개 쑰각을 λ†“λŠ” 6κ°€μ§€ μˆœμ„œ Γ— λͺ¨λ“  μœ„μΉ˜λ₯Ό μ „λΆ€ κ²€μ‚¬ν•΄μ„œ
13
+ **쀄을 κ°€μž₯ 많이 μ§€μš°λŠ” 배치**λ₯Ό μ°Ύμ•„μ£ΌλŠ” 정적 μ›Ήμ•±μž…λ‹ˆλ‹€. μ„œλ²„κ°€ μ—†κ³  λͺ¨λ“  계산은 λΈŒλΌμš°μ €(JS)μ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€.
14
+
15
+ ## Hugging Face Spaces에 λ°°ν¬ν•˜λŠ” 방법 (무료)
16
+
17
+ 1. https://huggingface.co/new-space 접속 β†’ 둜그인
18
+ 2. Space name μž…λ ₯ (예: `block-blast-solver`)
19
+ 3. **License**: μ›ν•˜λŠ” λŒ€λ‘œ (없어도 무방)
20
+ 4. **Space SDK**: **Static** 선택 (이게 μ€‘μš”! Gradio/Streamlit μ•„λ‹˜)
21
+ 5. **Space hardware**: κΈ°λ³Έ(Free Β· CPU basic) κ·ΈλŒ€λ‘œ 두면 λ©λ‹ˆλ‹€
22
+ 6. Create Space 클릭
23
+ 7. μƒμ„±λœ Space νŽ˜μ΄μ§€μ—μ„œ "Files" νƒ­ β†’ "Add file" β†’ "Upload files"둜
24
+ 이 폴더 μ•ˆμ˜ `index.html`κ³Ό `README.md`λ₯Ό κ·ΈλŒ€λ‘œ μ—…λ‘œλ“œ (λ˜λŠ” git으둜 push)
25
+ 8. μ»€λ°‹ν•˜λ©΄ 1λΆ„ μ΄λ‚΄λ‘œ `https://huggingface.co/spaces/{μ‚¬μš©μžλͺ…}/{Space이름}` μ£Όμ†Œμ—μ„œ λ°”λ‘œ μ‚¬μš© κ°€λŠ₯
26
+
27
+ ### git으둜 μ˜¬λ¦¬λŠ” 경우
28
+ ```bash
29
+ git clone https://huggingface.co/spaces/{μ‚¬μš©μžλͺ…}/{Space이름}
30
+ cp index.html README.md {Space이름}/
31
+ cd {Space이름}
32
+ git add .
33
+ git commit -m "block blast solver"
34
+ git push
35
+ ```
36
+
37
+ ## μ‚¬μš©λ²•
38
+
39
+ 1. μ™Όμͺ½ 8x8 λ³΄λ“œμ—μ„œ ν˜„μž¬ κ²Œμž„ ν™”λ©΄κ³Ό λ˜‘κ°™μ΄ μ±„μ›Œμ§„ 칸을 ν΄λ¦­ν•΄μ„œ ν‘œμ‹œ
40
+ 2. 였λ₯Έμͺ½ A/B/C 쑰각 νŽΈμ§‘κΈ°μ—μ„œ μ§€κΈˆ μ£Όμ–΄μ§„ 3개 쑰각 λͺ¨μ–‘을 κ·ΈλŒ€λ‘œ 그리기
41
+ (쑰각이 2개뿐이라면 λ‚˜λ¨Έμ§€ ν•˜λ‚˜λŠ” λΉ„μ›Œλ‘λ©΄ μžλ™μœΌλ‘œ μ œμ™Έλ©λ‹ˆλ‹€)
42
+ 3. "ν•΄λ‹΅ μ°ΎκΈ°" 클릭
43
+ 4. 1단계/2단계/3단계/μ΅œμ’… κ²°κ³Ό λ²„νŠΌμ„ λˆŒλŸ¬κ°€λ©° 어디에 μ–΄λ–€ μˆœμ„œλ‘œ 놓아야 ν•˜λŠ”μ§€ 확인
44
+
45
+ ## μ•Œκ³ λ¦¬μ¦˜
46
+
47
+ - 쑰각을 놓을 수 μžˆλŠ” λͺ¨λ“  μˆœμ„œ(μ΅œλŒ€ 3! = 6κ°€μ§€) Γ— 각 쑰각이 놓일 수 μžˆλŠ” λͺ¨λ“  (ν–‰, μ—΄) μœ„μΉ˜λ₯Ό
48
+ μž¬κ·€μ μœΌλ‘œ μ „λΆ€ μ‹œλ„ν•˜λŠ” 완전탐색(backtracking)μž…λ‹ˆλ‹€.
49
+ - 8Γ—8 λ³΄λ“œ + 쑰각 3개 규λͺ¨μ—μ„œλŠ” μ‹€μ œ νƒμƒ‰λŸ‰μ΄ λ§Žμ•„μ•Ό μˆ˜μ‹­λ§Œ μΌ€μ΄μŠ€ μˆ˜μ€€μ΄λΌ λΈŒλΌμš°μ €μ—μ„œ μ¦‰μ‹œ(λŒ€κ°œ μˆ˜μ‹­~수백 ms) κ³„μ‚°λ©λ‹ˆλ‹€.
50
+ - 평가 κΈ°μ€€: `총 μ§€μ›Œμ§„ 쀄 수`λ₯Ό μ΅œμš°μ„ μœΌλ‘œ, 동점이면 `μ΅œμ’…μ μœΌλ‘œ λ‚¨λŠ” 빈 μΉΈ 수`κ°€ λ§Žμ€(=λ³΄λ“œκ°€ 더 μ—¬μœ λ‘œμš΄) 배치λ₯Ό μ„ νƒν•©λ‹ˆλ‹€.
51
+ - 3개λ₯Ό λͺ¨λ‘ 놓을 수 μžˆλŠ” λ°°μΉ˜κ°€ μ—†μœΌλ©΄, μ΅œλŒ€ν•œ λ§Žμ€ 쑰각을 놓을 수 μžˆλŠ” λΆ€λΆ„ ν•΄λ₯Ό λŒ€μ‹  λ³΄μ—¬μ€λ‹ˆλ‹€.
index.html CHANGED
@@ -1,19 +1,774 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Block Blast Solver β€” λ‹€μŒ 수 계산기</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root{
12
+ --bg:#12141b;
13
+ --panel:#1b1e28;
14
+ --panel-2:#20232f;
15
+ --grid-line:#2c3040;
16
+ --grid-line-soft:#242733;
17
+ --ink:#eef0f6;
18
+ --muted:#8b90a4;
19
+ --muted-2:#5d6175;
20
+ --piece-a:#ff6f61;
21
+ --piece-a-dim:#5a2c28;
22
+ --piece-b:#3fd1c4;
23
+ --piece-b-dim:#1f4744;
24
+ --piece-c:#ffcb4d;
25
+ --piece-c-dim:#4d411c;
26
+ --filled:#3b3f52;
27
+ --clear:#7effa3;
28
+ --radius:10px;
29
+ }
30
+
31
+ *{box-sizing:border-box;}
32
+ html,body{margin:0;padding:0;}
33
+ body{
34
+ background:
35
+ linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0) 300px),
36
+ var(--bg);
37
+ color:var(--ink);
38
+ font-family:'Inter',sans-serif;
39
+ min-height:100vh;
40
+ padding:28px 18px 60px;
41
+ }
42
+
43
+ @media (prefers-reduced-motion: reduce){
44
+ *{animation-duration:0.001ms !important; transition-duration:0.001ms !important;}
45
+ }
46
+
47
+ .wrap{max-width:1080px;margin:0 auto;}
48
+
49
+ header.top{
50
+ display:flex;
51
+ align-items:flex-end;
52
+ justify-content:space-between;
53
+ gap:16px;
54
+ margin-bottom:26px;
55
+ padding-bottom:18px;
56
+ border-bottom:1px solid var(--grid-line-soft);
57
+ }
58
+ .brand{
59
+ font-family:'Space Grotesk',sans-serif;
60
+ font-weight:700;
61
+ font-size:22px;
62
+ letter-spacing:-0.01em;
63
+ display:flex;
64
+ align-items:baseline;
65
+ gap:10px;
66
+ }
67
+ .brand .mono-tag{
68
+ font-family:'JetBrains Mono',monospace;
69
+ font-size:11px;
70
+ font-weight:500;
71
+ color:var(--bg);
72
+ background:var(--clear);
73
+ padding:2px 7px;
74
+ border-radius:5px;
75
+ letter-spacing:0.02em;
76
+ }
77
+ .brand-sub{
78
+ color:var(--muted);
79
+ font-size:13px;
80
+ margin-top:4px;
81
+ }
82
+ .reset-btn{
83
+ background:none;
84
+ border:1px solid var(--grid-line);
85
+ color:var(--muted);
86
+ font-family:'Inter',sans-serif;
87
+ font-size:13px;
88
+ padding:8px 14px;
89
+ border-radius:8px;
90
+ cursor:pointer;
91
+ transition:border-color .15s, color .15s;
92
+ }
93
+ .reset-btn:hover{border-color:var(--muted-2); color:var(--ink);}
94
+ .reset-btn:focus-visible{outline:2px solid var(--clear); outline-offset:2px;}
95
+
96
+ main.layout{
97
+ display:grid;
98
+ grid-template-columns:minmax(0,1fr) 300px;
99
+ gap:22px;
100
+ align-items:start;
101
+ }
102
+ @media (max-width:820px){
103
+ main.layout{grid-template-columns:1fr;}
104
+ }
105
+
106
+ .panel{
107
+ background:var(--panel);
108
+ border:1px solid var(--grid-line-soft);
109
+ border-radius:14px;
110
+ padding:20px;
111
+ }
112
+ .panel-title{
113
+ font-family:'Space Grotesk',sans-serif;
114
+ font-weight:600;
115
+ font-size:14px;
116
+ letter-spacing:0.02em;
117
+ text-transform:uppercase;
118
+ color:var(--muted);
119
+ margin:0 0 14px;
120
+ display:flex;
121
+ align-items:center;
122
+ justify-content:space-between;
123
+ }
124
+ .panel-title .hint{
125
+ font-family:'Inter',sans-serif;
126
+ text-transform:none;
127
+ font-weight:400;
128
+ font-size:12px;
129
+ color:var(--muted-2);
130
+ letter-spacing:0;
131
+ }
132
+
133
+ /* ---- 8x8 board ---- */
134
+ .board8{
135
+ display:grid;
136
+ grid-template-columns:repeat(8, 1fr);
137
+ gap:3px;
138
+ aspect-ratio:1/1;
139
+ max-width:460px;
140
+ margin:0 auto;
141
+ background:var(--grid-line-soft);
142
+ padding:3px;
143
+ border-radius:8px;
144
+ }
145
+ .cell{
146
+ aspect-ratio:1/1;
147
+ border-radius:4px;
148
+ background:var(--panel-2);
149
+ cursor:pointer;
150
+ border:1px solid transparent;
151
+ position:relative;
152
+ transition:background .1s;
153
+ }
154
+ .cell:hover{background:#282c3a;}
155
+ .cell:focus-visible{outline:2px solid var(--clear); outline-offset:-2px;}
156
+ .cell.filled{background:var(--filled);}
157
+ .cell.piece-a{background:var(--piece-a);}
158
+ .cell.piece-b{background:var(--piece-b);}
159
+ .cell.piece-c{background:var(--piece-c);}
160
+ .cell.will-clear::after{
161
+ content:"";
162
+ position:absolute; inset:0;
163
+ border:2px solid var(--clear);
164
+ border-radius:4px;
165
+ box-shadow:0 0 8px rgba(126,255,163,0.55);
166
+ }
167
+ .cell .num{
168
+ position:absolute; top:1px; right:2px;
169
+ font-family:'JetBrains Mono',monospace;
170
+ font-size:9px;
171
+ color:rgba(0,0,0,0.55);
172
+ }
173
+
174
+ .board-legend{
175
+ display:flex;
176
+ gap:16px;
177
+ justify-content:center;
178
+ margin-top:12px;
179
+ font-size:11px;
180
+ color:var(--muted);
181
+ flex-wrap:wrap;
182
+ }
183
+ .board-legend span{display:inline-flex; align-items:center; gap:5px;}
184
+ .swatch{width:10px;height:10px;border-radius:3px;display:inline-block;}
185
+
186
+ /* ---- piece editors ---- */
187
+ .pieces{display:flex; flex-direction:column; gap:14px;}
188
+ .piece-card{
189
+ background:var(--panel);
190
+ border:1px solid var(--grid-line-soft);
191
+ border-radius:14px;
192
+ padding:16px;
193
+ }
194
+ .piece-head{
195
+ display:flex; align-items:center; justify-content:space-between;
196
+ margin-bottom:10px;
197
+ }
198
+ .piece-label{
199
+ font-family:'Space Grotesk',sans-serif;
200
+ font-weight:600;
201
+ font-size:13px;
202
+ display:flex; align-items:center; gap:8px;
203
+ }
204
+ .piece-dot{width:9px;height:9px;border-radius:50%;}
205
+ .piece-clear{
206
+ background:none; border:none; color:var(--muted-2);
207
+ font-size:11px; cursor:pointer; font-family:'Inter',sans-serif;
208
+ padding:2px 4px;
209
+ }
210
+ .piece-clear:hover{color:var(--ink);}
211
+ .piece-grid{
212
+ display:grid;
213
+ grid-template-columns:repeat(5, 1fr);
214
+ gap:2px;
215
+ width:120px;
216
+ margin:0 auto;
217
+ background:var(--grid-line-soft);
218
+ padding:2px;
219
+ border-radius:6px;
220
+ }
221
+ .pcell{
222
+ aspect-ratio:1/1;
223
+ background:var(--panel-2);
224
+ border-radius:3px;
225
+ cursor:pointer;
226
+ border:none;
227
+ }
228
+ .pcell:hover{background:#282c3a;}
229
+ .pcell:focus-visible{outline:2px solid var(--clear); outline-offset:-1px;}
230
+ .pcell.on{background:var(--piece-color, var(--piece-a));}
231
+
232
+ .solve-row{
233
+ margin-top:6px;
234
+ }
235
+ .solve-btn{
236
+ width:100%;
237
+ background:var(--clear);
238
+ color:#0b1710;
239
+ border:none;
240
+ font-family:'Space Grotesk',sans-serif;
241
+ font-weight:700;
242
+ font-size:15px;
243
+ padding:13px;
244
+ border-radius:10px;
245
+ cursor:pointer;
246
+ transition:transform .08s, box-shadow .15s;
247
+ box-shadow:0 0 0 rgba(126,255,163,0);
248
+ }
249
+ .solve-btn:hover{box-shadow:0 4px 18px rgba(126,255,163,0.25);}
250
+ .solve-btn:active{transform:scale(0.98);}
251
+ .solve-btn:focus-visible{outline:2px solid var(--ink); outline-offset:2px;}
252
+ .solve-btn:disabled{background:var(--grid-line); color:var(--muted-2); cursor:default; box-shadow:none;}
253
+
254
+ .status-line{
255
+ font-family:'JetBrains Mono',monospace;
256
+ font-size:12px;
257
+ color:var(--muted);
258
+ margin-top:10px;
259
+ min-height:16px;
260
+ text-align:center;
261
+ }
262
+
263
+ /* ---- results ---- */
264
+ .results{margin-top:22px;}
265
+ .steps-row{
266
+ display:flex; gap:8px; flex-wrap:wrap;
267
+ margin-bottom:16px;
268
+ }
269
+ .step-chip{
270
+ font-family:'JetBrains Mono',monospace;
271
+ font-size:13px;
272
+ background:var(--panel-2);
273
+ border:1px solid var(--grid-line);
274
+ color:var(--muted);
275
+ padding:8px 14px;
276
+ border-radius:8px;
277
+ cursor:pointer;
278
+ transition:all .12s;
279
+ }
280
+ .step-chip:hover{color:var(--ink); border-color:var(--muted-2);}
281
+ .step-chip.active{
282
+ background:var(--clear);
283
+ color:#0b1710;
284
+ border-color:var(--clear);
285
+ font-weight:700;
286
+ }
287
+ .step-chip:focus-visible{outline:2px solid var(--clear); outline-offset:2px;}
288
+
289
+ .step-desc{
290
+ font-size:14px;
291
+ line-height:1.6;
292
+ color:var(--ink);
293
+ background:var(--panel-2);
294
+ border-left:3px solid var(--clear);
295
+ padding:12px 16px;
296
+ border-radius:0 8px 8px 0;
297
+ margin-bottom:16px;
298
+ }
299
+ .step-desc .coord{
300
+ font-family:'JetBrains Mono',monospace;
301
+ color:var(--clear);
302
+ }
303
+
304
+ .score-strip{
305
+ display:flex;
306
+ gap:22px;
307
+ flex-wrap:wrap;
308
+ font-family:'JetBrains Mono',monospace;
309
+ font-size:12px;
310
+ color:var(--muted);
311
+ padding-top:4px;
312
+ border-top:1px solid var(--grid-line-soft);
313
+ }
314
+ .score-strip b{color:var(--ink); font-weight:700;}
315
+
316
+ .empty-note{
317
+ color:var(--muted);
318
+ font-size:13px;
319
+ line-height:1.7;
320
+ text-align:center;
321
+ padding:20px 10px;
322
+ }
323
+
324
+ footer.foot{
325
+ max-width:1080px;
326
+ margin:34px auto 0;
327
+ color:var(--muted-2);
328
+ font-size:12px;
329
+ text-align:center;
330
+ line-height:1.6;
331
+ }
332
+ </style>
333
+ </head>
334
+ <body>
335
+ <div class="wrap">
336
+
337
+ <header class="top">
338
+ <div>
339
+ <div class="brand">Block Blast Solver <span class="mono-tag">SEARCH</span></div>
340
+ <div class="brand-sub">ν˜„μž¬ λ³΄λ“œμ™€ 3개 쑰각을 그리면, μ™„μ „νƒμƒ‰μœΌλ‘œ κ°€μž₯ 쀄을 많이 μ§€μš°λŠ” 배치 μˆœμ„œλ₯Ό μ°Ύμ•„λ“œλ €μš”.</div>
341
+ </div>
342
+ <button class="reset-btn" id="resetAllBtn">전체 μ΄ˆκΈ°ν™”</button>
343
+ </header>
344
+
345
+ <main class="layout">
346
+ <section class="panel">
347
+ <div class="panel-title">λ³΄λ“œ μƒνƒœ <span class="hint">칸을 클릭해 μ±„μ›Œμ§„ 블둝을 ν‘œμ‹œν•˜μ„Έμš”</span></div>
348
+ <div class="board8" id="board"></div>
349
+ <div class="board-legend">
350
+ <span><span class="swatch" style="background:var(--filled)"></span>μ±„μ›Œμ§„ μΉΈ</span>
351
+ <span><span class="swatch" style="background:var(--piece-a)"></span>쑰각 A</span>
352
+ <span><span class="swatch" style="background:var(--piece-b)"></span>��각 B</span>
353
+ <span><span class="swatch" style="background:var(--piece-c)"></span>쑰각 C</span>
354
+ </div>
355
+
356
+ <div class="results" id="resultsBlock" style="display:none;">
357
+ <div class="steps-row" id="stepsRow"></div>
358
+ <div class="step-desc" id="stepDesc"></div>
359
+ <div class="score-strip" id="scoreStrip"></div>
360
+ </div>
361
+
362
+ <div class="empty-note" id="emptyNote">
363
+ μ•„λž˜μ—μ„œ μ„Έ 쑰각 λͺ¨μ–‘을 그리고 <b style="color:var(--ink)">ν•΄λ‹΅ μ°ΎκΈ°</b>λ₯Ό λˆ„λ₯΄λ©΄ μ—¬κΈ° κ²°κ³Όκ°€ ν‘œμ‹œλ©λ‹ˆλ‹€.
364
+ </div>
365
+ </section>
366
+
367
+ <aside class="pieces">
368
+ <div class="piece-card">
369
+ <div class="piece-head">
370
+ <div class="piece-label"><span class="piece-dot" style="background:var(--piece-a)"></span>쑰각 A</div>
371
+ <button class="piece-clear" data-clear="a">μ§€μš°κΈ°</button>
372
+ </div>
373
+ <div class="piece-grid" id="piece-a" style="--piece-color:var(--piece-a)"></div>
374
+ </div>
375
+ <div class="piece-card">
376
+ <div class="piece-head">
377
+ <div class="piece-label"><span class="piece-dot" style="background:var(--piece-b)"></span>쑰각 B</div>
378
+ <button class="piece-clear" data-clear="b">μ§€μš°κΈ°</button>
379
+ </div>
380
+ <div class="piece-grid" id="piece-b" style="--piece-color:var(--piece-b)"></div>
381
+ </div>
382
+ <div class="piece-card">
383
+ <div class="piece-head">
384
+ <div class="piece-label"><span class="piece-dot" style="background:var(--piece-c)"></span>쑰각 C</div>
385
+ <button class="piece-clear" data-clear="c">μ§€μš°κΈ°</button>
386
+ </div>
387
+ <div class="piece-grid" id="piece-c" style="--piece-color:var(--piece-c)"></div>
388
+ </div>
389
+
390
+ <div class="solve-row">
391
+ <button class="solve-btn" id="solveBtn">ν•΄λ‹΅ μ°ΎκΈ°</button>
392
+ <div class="status-line" id="statusLine"></div>
393
+ </div>
394
+ </aside>
395
+ </main>
396
+
397
+ <footer class="foot">
398
+ λ³΄λ“œ 클릭 = μ±„μ›Œμ§„ μΉΈ ν† κΈ€ Β· 쑰각 νŽΈμ§‘κΈ° 클릭 = λͺ¨μ–‘ 그리기 Β· 탐색은 λΈŒλΌμš°μ €μ—μ„œλ§Œ μ‹€ν–‰λ˜λ©° μ„œλ²„λ‘œ μ „μ†‘λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
399
+ </footer>
400
+ </div>
401
+
402
+ <script>
403
+ (function(){
404
+ const N = 8;
405
+ const PIECE_GRID = 5;
406
+ const pieceKeys = ['a','b','c'];
407
+ const pieceColorVar = {a:'var(--piece-a)', b:'var(--piece-b)', c:'var(--piece-c)'};
408
+ const pieceNameKo = {a:'A', b:'B', c:'C'};
409
+
410
+ // ---- state ----
411
+ let board = Array.from({length:N}, ()=>Array(N).fill(0));
412
+ let pieceGrids = {
413
+ a: Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0)),
414
+ b: Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0)),
415
+ c: Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0)),
416
+ };
417
+ let lastSolution = null; // {steps:[{piece,r,c,offsets,boardBeforeClear,clearRows,clearCols,boardAfterClear}], totalLines, finalEmpty}
418
+
419
+ // ---- DOM refs ----
420
+ const boardEl = document.getElementById('board');
421
+ const statusLine = document.getElementById('statusLine');
422
+ const resultsBlock = document.getElementById('resultsBlock');
423
+ const emptyNote = document.getElementById('emptyNote');
424
+ const stepsRow = document.getElementById('stepsRow');
425
+ const stepDesc = document.getElementById('stepDesc');
426
+ const scoreStrip = document.getElementById('scoreStrip');
427
+
428
+ // ---- build board grid ----
429
+ const boardCells = [];
430
+ for(let r=0;r<N;r++){
431
+ const row=[];
432
+ for(let c=0;c<N;c++){
433
+ const cell = document.createElement('button');
434
+ cell.className='cell';
435
+ cell.type='button';
436
+ cell.setAttribute('aria-label', `${r+1}ν–‰ ${c+1}μ—΄`);
437
+ cell.addEventListener('click', ()=>{
438
+ board[r][c] = board[r][c] ? 0 : 1;
439
+ clearSolution();
440
+ renderBoard();
441
+ });
442
+ boardEl.appendChild(cell);
443
+ row.push(cell);
444
+ }
445
+ boardCells.push(row);
446
+ }
447
+
448
+ // ---- build piece editors ----
449
+ const pieceCellEls = {a:[], b:[], c:[]};
450
+ pieceKeys.forEach(k=>{
451
+ const el = document.getElementById('piece-'+k);
452
+ for(let r=0;r<PIECE_GRID;r++){
453
+ const rowEls=[];
454
+ for(let c=0;c<PIECE_GRID;c++){
455
+ const b = document.createElement('button');
456
+ b.type='button';
457
+ b.className='pcell';
458
+ b.setAttribute('aria-label', `${pieceNameKo[k]} 쑰각 ${r+1},${c+1}`);
459
+ b.addEventListener('click', ()=>{
460
+ pieceGrids[k][r][c] = pieceGrids[k][r][c] ? 0 : 1;
461
+ clearSolution();
462
+ renderPiece(k);
463
+ });
464
+ el.appendChild(b);
465
+ rowEls.push(b);
466
+ }
467
+ pieceCellEls[k].push(rowEls);
468
+ }
469
+ });
470
+
471
+ document.querySelectorAll('.piece-clear').forEach(btn=>{
472
+ btn.addEventListener('click', ()=>{
473
+ const k = btn.dataset.clear;
474
+ pieceGrids[k] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0));
475
+ clearSolution();
476
+ renderPiece(k);
477
+ });
478
+ });
479
+
480
+ document.getElementById('resetAllBtn').addEventListener('click', ()=>{
481
+ board = Array.from({length:N}, ()=>Array(N).fill(0));
482
+ pieceKeys.forEach(k=> pieceGrids[k] = Array.from({length:PIECE_GRID}, ()=>Array(PIECE_GRID).fill(0)));
483
+ clearSolution();
484
+ renderBoard();
485
+ pieceKeys.forEach(renderPiece);
486
+ statusLine.textContent='';
487
+ });
488
+
489
+ document.getElementById('solveBtn').addEventListener('click', runSolve);
490
+
491
+ // ---- rendering ----
492
+ function renderBoard(overlay){
493
+ // overlay: optional {board: 8x8 with 0/'a'/'b'/'c', willClear:Set of "r,c"}
494
+ for(let r=0;r<N;r++){
495
+ for(let c=0;c<N;c++){
496
+ const cell = boardCells[r][c];
497
+ cell.className='cell';
498
+ if(overlay){
499
+ const v = overlay.board[r][c];
500
+ if(v==='a'||v==='b'||v==='c') cell.classList.add('piece-'+v);
501
+ else if(v===1) cell.classList.add('filled');
502
+ if(overlay.willClear && overlay.willClear.has(r+','+c)) cell.classList.add('will-clear');
503
+ } else {
504
+ if(board[r][c]) cell.classList.add('filled');
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ function renderPiece(k){
511
+ for(let r=0;r<PIECE_GRID;r++){
512
+ for(let c=0;c<PIECE_GRID;c++){
513
+ const el = pieceCellEls[k][r][c];
514
+ el.classList.toggle('on', !!pieceGrids[k][r][c]);
515
+ }
516
+ }
517
+ }
518
+
519
+ function clearSolution(){
520
+ lastSolution = null;
521
+ resultsBlock.style.display='none';
522
+ emptyNote.style.display='block';
523
+ }
524
+
525
+ renderBoard();
526
+ pieceKeys.forEach(renderPiece);
527
+
528
+ // ---- shape trimming ----
529
+ function trimShape(grid){
530
+ let minR=99,maxR=-1,minC=99,maxC=-1;
531
+ for(let r=0;r<grid.length;r++){
532
+ for(let c=0;c<grid[0].length;c++){
533
+ if(grid[r][c]){
534
+ if(r<minR)minR=r; if(r>maxR)maxR=r;
535
+ if(c<minC)minC=c; if(c>maxC)maxC=c;
536
+ }
537
+ }
538
+ }
539
+ if(maxR===-1) return null; // empty piece
540
+ const offsets=[];
541
+ for(let r=minR;r<=maxR;r++){
542
+ for(let c=minC;c<=maxC;c++){
543
+ if(grid[r][c]) offsets.push([r-minR, c-minC]);
544
+ }
545
+ }
546
+ return {offsets, h:maxR-minR+1, w:maxC-minC+1};
547
+ }
548
+
549
+ // ---- board ops ----
550
+ function canPlace(bd, offsets, r, c){
551
+ for(const [dr,dc] of offsets){
552
+ const rr=r+dr, cc=c+dc;
553
+ if(rr<0||rr>=N||cc<0||cc>=N) return false;
554
+ if(bd[rr][cc]) return false;
555
+ }
556
+ return true;
557
+ }
558
+
559
+ function cloneBoard(bd){
560
+ return bd.map(row=>row.slice());
561
+ }
562
+
563
+ function place(bd, offsets, r, c){
564
+ for(const [dr,dc] of offsets){
565
+ bd[r+dr][c+dc]=1;
566
+ }
567
+ }
568
+
569
+ function findClears(bd){
570
+ const rows=[], cols=[];
571
+ for(let r=0;r<N;r++){
572
+ let full=true;
573
+ for(let c=0;c<N;c++) if(!bd[r][c]){full=false;break;}
574
+ if(full) rows.push(r);
575
+ }
576
+ for(let c=0;c<N;c++){
577
+ let full=true;
578
+ for(let r=0;r<N;r++) if(!bd[r][c]){full=false;break;}
579
+ if(full) cols.push(c);
580
+ }
581
+ return {rows, cols};
582
+ }
583
+
584
+ function applyClear(bd, rows, cols){
585
+ for(const r of rows) for(let c=0;c<N;c++) bd[r][c]=0;
586
+ for(const c of cols) for(let r=0;r<N;r++) bd[r][c]=0;
587
+ }
588
+
589
+ function countEmpty(bd){
590
+ let n=0;
591
+ for(let r=0;r<N;r++) for(let c=0;c<N;c++) if(!bd[r][c]) n++;
592
+ return n;
593
+ }
594
+
595
+ // ---- solver (backtracking over all orderings x all positions) ----
596
+ function solve(initialBoard, pieces){
597
+ // pieces: [{key, offsets, h, w}]
598
+ let best = null; // {steps, totalLines, finalEmpty, score}
599
+ let bestPartial = null; // fallback if no full solution: {steps, placedCount, totalLines, score}
600
+
601
+ function evaluate(steps, totalLines, finalBoard){
602
+ const finalEmpty = countEmpty(finalBoard);
603
+ const score = totalLines*1000 + finalEmpty;
604
+ return {steps: steps.slice(), totalLines, finalEmpty, score};
605
+ }
606
+
607
+ function recurse(bd, remaining, steps, totalLines){
608
+ if(remaining.length===0){
609
+ const result = evaluate(steps, totalLines, bd);
610
+ if(!best || result.score > best.score) best = result;
611
+ return;
612
+ }
613
+ // track partial best in case nothing completes
614
+ {
615
+ const partialScore = totalLines*1000 + countEmpty(bd) + steps.length*2000; // prioritize placing more pieces
616
+ if(!bestPartial || partialScore > bestPartial.score){
617
+ bestPartial = {steps: steps.slice(), placedCount: steps.length, totalLines, score: partialScore};
618
+ }
619
+ }
620
+ for(let i=0;i<remaining.length;i++){
621
+ const piece = remaining[i];
622
+ const restPieces = remaining.slice(0,i).concat(remaining.slice(i+1));
623
+ const maxR = N - piece.h, maxC = N - piece.w;
624
+ for(let r=0;r<=maxR;r++){
625
+ for(let c=0;c<=maxC;c++){
626
+ if(canPlace(bd, piece.offsets, r, c)){
627
+ const nb = cloneBoard(bd);
628
+ place(nb, piece.offsets, r, c);
629
+ const beforeClear = cloneBoard(nb);
630
+ const {rows, cols} = findClears(nb);
631
+ applyClear(nb, rows, cols);
632
+ const linesThisStep = rows.length + cols.length;
633
+ steps.push({
634
+ pieceKey: piece.key,
635
+ r, c,
636
+ offsets: piece.offsets,
637
+ boardBeforeClear: beforeClear,
638
+ clearRows: rows,
639
+ clearCols: cols,
640
+ boardAfterClear: cloneBoard(nb),
641
+ linesThisStep
642
+ });
643
+ recurse(nb, restPieces, steps, totalLines + linesThisStep);
644
+ steps.pop();
645
+ }
646
+ }
647
+ }
648
+ }
649
+ }
650
+
651
+ recurse(cloneBoard(initialBoard), pieces, [], 0);
652
+ return {best, bestPartial};
653
+ }
654
+
655
+ function runSolve(){
656
+ // prepare pieces
657
+ const pieces = [];
658
+ let anyEmpty = false;
659
+ for(const k of pieceKeys){
660
+ const trimmed = trimShape(pieceGrids[k]);
661
+ if(!trimmed){ anyEmpty = true; continue; }
662
+ pieces.push({key:k, offsets:trimmed.offsets, h:trimmed.h, w:trimmed.w});
663
+ }
664
+ if(pieces.length===0){
665
+ statusLine.textContent = '쑰각을 μ΅œμ†Œ 1개 이상 κ·Έλ €μ£Όμ„Έμš”.';
666
+ return;
667
+ }
668
+ if(anyEmpty){
669
+ statusLine.textContent = 'λΉ„μ–΄ μžˆλŠ” 쑰각은 이번 νƒμƒ‰μ—μ„œ μ œμ™ΈλΌμš”. (ν•„μš”ν•˜λ©΄ μ„Έ 쑰각을 λͺ¨λ‘ μ±„μ›Œμ£Όμ„Έμš”)';
670
+ } else {
671
+ statusLine.textContent = '';
672
+ }
673
+
674
+ statusLine.textContent += (statusLine.textContent?' Β· ':'') + '탐색 쀑...';
675
+ document.getElementById('solveBtn').disabled = true;
676
+
677
+ setTimeout(()=>{
678
+ const t0 = performance.now();
679
+ const {best, bestPartial} = solve(board, pieces);
680
+ const t1 = performance.now();
681
+ document.getElementById('solveBtn').disabled = false;
682
+
683
+ if(best){
684
+ lastSolution = best;
685
+ statusLine.textContent = `탐색 μ™„λ£Œ (${Math.round(t1-t0)}ms) Β· 전체 배치 κ°€λŠ₯`;
686
+ showSolution(best, pieces.length);
687
+ } else if(bestPartial && bestPartial.steps.length>0){
688
+ lastSolution = bestPartial;
689
+ statusLine.textContent = `탐색 μ™„λ£Œ (${Math.round(t1-t0)}ms) Β· 쑰각 ${pieces.length}개λ₯Ό λͺ¨λ‘ 놓을 방법은 μ—†μ–΄μš”`;
690
+ showSolution(bestPartial, pieces.length, true);
691
+ } else {
692
+ lastSolution = null;
693
+ statusLine.textContent = `탐색 μ™„λ£Œ (${Math.round(t1-t0)}ms) Β· 놓을 수 μžˆλŠ” 쑰각이 μ—†μ–΄μš”`;
694
+ resultsBlock.style.display='none';
695
+ emptyNote.style.display='block';
696
+ emptyNote.textContent = 'μ§€κΈˆ λ³΄λ“œ μƒνƒœλ‘œλŠ” κ·Έλ €μ£Όμ‹  쑰각을 ν•˜λ‚˜λ„ 놓을 수 μ—†μ–΄μš”. λ³΄λ“œλ‚˜ 쑰각 λͺ¨μ–‘을 λ‹€μ‹œ ν™•μΈν•΄μ£Όμ„Έμš”.';
697
+ }
698
+ }, 20);
699
+ }
700
+
701
+ // ---- display solution ----
702
+ let activeStepIdx = 0;
703
+
704
+ function showSolution(sol, totalPiecesRequested, isPartial){
705
+ resultsBlock.style.display='block';
706
+ emptyNote.style.display='none';
707
+
708
+ stepsRow.innerHTML='';
709
+ sol.steps.forEach((step, idx)=>{
710
+ const chip = document.createElement('button');
711
+ chip.type='button';
712
+ chip.className='step-chip';
713
+ chip.textContent = `${idx+1}단계 Β· ${pieceNameKo[step.pieceKey]}`;
714
+ chip.addEventListener('click', ()=> setActiveStep(idx));
715
+ stepsRow.appendChild(chip);
716
+ });
717
+ const finalChip = document.createElement('button');
718
+ finalChip.type='button';
719
+ finalChip.className='step-chip';
720
+ finalChip.textContent = 'μ΅œμ’… κ²°κ³Ό';
721
+ finalChip.addEventListener('click', ()=> setActiveStep(sol.steps.length));
722
+ stepsRow.appendChild(finalChip);
723
+
724
+ function setActiveStep(idx){
725
+ activeStepIdx = idx;
726
+ Array.from(stepsRow.children).forEach((el,i)=> el.classList.toggle('active', i===idx));
727
+
728
+ if(idx < sol.steps.length){
729
+ const step = sol.steps[idx];
730
+ const overlayBoard = step.boardBeforeClear.map(row=>row.slice());
731
+ // mark piece cells with its color letter for rendering
732
+ for(const [dr,dc] of step.offsets){
733
+ overlayBoard[step.r+dr][step.c+dc] = step.pieceKey;
734
+ }
735
+ const willClear = new Set();
736
+ for(const r of step.clearRows) for(let c=0;c<N;c++) willClear.add(r+','+c);
737
+ for(const c of step.clearCols) for(let r=0;r<N;r++) willClear.add(r+','+c);
738
+ renderBoard({board:overlayBoard, willClear});
739
+
740
+ const lineTxt = step.linesThisStep>0
741
+ ? `이 배치둜 <b class="coord">${step.linesThisStep}쀄</b>이 μ§€μ›Œμ Έμš”.`
742
+ : `이 λ°°μΉ˜λ‘œλŠ” 아직 쀄이 μ§€μ›Œμ§€μ§€ μ•Šμ•„μš”.`;
743
+ stepDesc.innerHTML = `<b>${idx+1}단계</b> β€” 쑰각 <b class="coord">${pieceNameKo[step.pieceKey]}</b>λ₯Ό `
744
+ + `<span class="coord">${step.r+1}ν–‰ ${step.c+1}μ—΄</span>(μ™Όμͺ½ μœ„ κΈ°μ€€)에 λ†“μœΌμ„Έμš”. ${lineTxt}`;
745
+ } else {
746
+ // final state
747
+ const last = sol.steps[sol.steps.length-1];
748
+ renderBoard({board: last.boardAfterClear});
749
+ stepDesc.innerHTML = `<b>μ΅œμ’… λ³΄λ“œ</b> β€” λͺ¨λ“  λ°°μΉ˜μ™€ 쀄 μ§€μš°κΈ°κ°€ λλ‚œ λ’€μ˜ μƒνƒœμ˜ˆμš”.`;
750
+ }
751
+ }
752
+
753
+ const totalLines = sol.totalLines;
754
+ const placedCount = sol.steps.length;
755
+ scoreStrip.innerHTML = `
756
+ <span>배치된 쑰각: <b>${placedCount} / ${totalPiecesRequested}</b></span>
757
+ <span>총 μ§€μ›Œμ§„ 쀄: <b>${totalLines}</b></span>
758
+ <span>탐색 방식: <b>완전탐색 (λ°±νŠΈλž˜ν‚Ή)</b></span>
759
+ `;
760
+ if(isPartial){
761
+ const warn = document.createElement('div');
762
+ warn.style.color = 'var(--piece-a)';
763
+ warn.style.marginTop = '6px';
764
+ warn.textContent = '⚠ 3개λ₯Ό λͺ¨λ‘ λ†“μœΌλ©΄ λ³΄λ“œκ°€ λ§‰ν˜€μš”. ν‘œμ‹œλœ 것은 μ΅œλŒ€ν•œ 많이 놓을 수 μžˆλŠ” λ°°μΉ˜μ˜ˆμš”.';
765
+ scoreStrip.appendChild(warn);
766
+ }
767
+
768
+ setActiveStep(0);
769
+ }
770
+
771
+ })();
772
+ </script>
773
+ </body>
774
  </html>