μΉκ΅¬ 2~5λͺ
μ΄ κ³ μ€ν±Β·ν¬μ»€Β·νλΌλ₯Ό ν λ,
λκ° λꡬμκ² μΌλ§λ₯Ό 보λ΄μΌ νλμ§ λλ λ ν νλ©΄μμ μλ €μ£Όλ μΉμ±
"λμ μ μμ΄λ κ³μ°μ μ ννκ²"
μ€μΉ μμ΄ λ§ν¬λ§ μ΄λ©΄ λ©λλ€ Β· ν°μμ "ν νλ©΄μ μΆκ°"νλ©΄ μ±μ²λΌ μλλ€
μ μ리μμ κ³ μ€ν± ν ν λλ λλ§λ€ λ²μ΄μ§λ μΌ:
"μΌ μ§κΈ λ΄κ° μΌλ§ μμμ§?"
"μκΉ νΌλ° κ·Έκ±° κ³μ°νλ?"
"μ κΉλ§, κ·ΈλΌ κ²°κ΅ λκ° λꡬνν 보λ΄λ κ±°μΌ?"
νλμ μ°μ μ΄κ±Έ λμ ν΄ μ€λλ€. ν ν λλ λλ§λ€ λκ° λ¬λμ§ + λͺ μ μΈμ§ λ λ²λ§ ννλ©΄ μμ‘μ΄ μ€μκ°μΌλ‘ κ΅΄λ¬κ°κ³ , μ리λ₯Ό νν λ "μν¬ β μ¬νΈ 12,400μ" μ²λΌ μ‘κΈ λͺ©λ‘μΌλ‘ λ± λ¨μ΄μ§κ² μ λ¦¬ν΄ μ€λλ€.
μμ‘μ ν©μ μΈμ λ 0μ΄λ―λ‘, μ‘κΈμ μΈμ β 1 건μ λμ§ μμ΅λλ€. 3λͺ
μ΄λ©΄ 2건, 5λͺ
μ΄λ©΄ 4건μ
λλ€.
Important
λμν©λλ€. npx serve public μΌλ‘ λμμ λ°λ‘ μΉ μ μκ³ , ν
μ€νΈ 108κ°κ° ν΅κ³Όν©λλ€ (npm test).
λ¬Έμλ λ νΈμ΄κ³ μν μ΄ λ€λ¦
λλ€.
SPEC.mdβ 무μμ λ§λλκ°. λ°μ΄ν° λͺ¨λΈΒ·κ³μ° 곡μΒ·νλ©΄ λͺ μΈΒ·κ²μ¦λ ν μ€νΈ κΈ°λκ°μ€κ³μβ μ΄λ€ κ΅¬μ‘°λ‘ λ§λ€κ³ μ΄λμ μ¬λ¦¬λκ°. μΉμ± λ λ€μ΄ν°λΈ μ±, λͺ¨λ κ²½κ³, μ μ₯ λ°©μ, Cloudflare λ°°ν¬ νμdocs/design/β μ΄λ»κ² 보μ΄λκ°. νλ©΄ μμκ³Ό λμμΈ μμ€ν (.dc.html)
λͺ μΈ 11μ₯μ κΈ°λκ° νλ κ·Έλλ‘ ν μ€νΈκ° λμ΄ μμ΅λλ€ β κ³μ°μ κ³ μΉ λ μ¬κΈ°λΆν° 보μΈμ.
| λ¨κ³ | μν |
|---|---|
μ€κ³ λͺ
μΈ (SPEC.md) |
β |
| μν€ν μ²Β·λ°°ν¬ μ€κ³μ | β |
μ μ° μκ³ λ¦¬μ¦ settle.js + ν
μ€νΈ |
β |
κ²μλ³ κ³μ° rules.js + ν
μ€νΈ |
β |
μ μ₯/볡μ store.js |
β |
| κ³ μ€ν± μ λ ₯ νλ©΄ | β |
| μ μ° νλ©΄ Β· 곡μ ν μ€νΈ | β |
| ν¬μ»€ Β· νλΌ νλ©΄ | β |
| SVG λμ κ·Έλν Β· ν΅κ³ | β |
| λ£° μ€μ νλ©΄ | β |
| PWA (ννλ©΄ μΆκ° / μ€νλΌμΈ) | β |
| μ€μ ν°μμ νμΈ | β³ |
pandon.blackpet.workers.dev λ₯Ό μ΄λ©΄ λ°λ‘ μΉ μ μμ΅λλ€. μ§μ λλ¦¬λ €λ©΄:
npx serve public # μ΄μ΄μ λ°λ‘ μΉκΈ° (λΉλ μμ)
npm test # κ³μ°Β·μ μ₯Β·κ·Έλν 108κ°
npx wrangler deploy # Cloudflare λ°°ν¬. --env μμ΄ ν μ€ν νμ λκ° λ¬μ΄? β λͺ μ ? β νμΈ μΈ λ²μ
λλ€. νΌλ°Β·κ³ λ° κ°μ λ°°μ μΉ©μ
μ λΆ κΊΌμ§ μ±λ‘ μμνκ³ , ν΄λΉλ λλ§ λλ¦
λλ€.
μ΄ νλ‘μ νΈμμ νννμ§ μλ κ²λ€μ λλ€.
νλ²ν ν νμ μΉμ ν β μ μ ν β νμΈ μΈ λ²μΌλ‘ λλ©λλ€.
νΌλ°Β·κ΄λ°Β·κ³ λ° κ°μ λ°°μ μΉ©μ κΈ°λ³Έμ΄ μ λΆ κΊΌμ Έ μκ³ , ν΄λΉλ λλ§ λλ¦
λλ€.
μ κΈ°λ₯μ΄ ν μλ₯Ό λλ¦°λ€λ©΄ κΈ°λ₯μ λ£λ λμ κΈ°λ³Έκ°μ λ€μ μ€κ³ν©λλ€.
λ°€μ μ λ§μλ©° λ³Έλ€λ μ μ λΌ λ€ν¬ ν
λ§ κ³ μ μ
λλ€.
ν°μΉ νκ²μ μ΅μ 56px, κΈμ‘μ tabular-nums λ‘ μλ¦Ώμλ₯Ό κ³ μ ν΄μ
μ«μκ° κ°±μ λ λ λ μ΄μμμ΄ νλ€λ¦¬μ§ μμ΅λλ€.
κ³ μ€ν±κ³Ό νλΌλ λͺ¨μλ§λ€ λ£°μ΄ λ€λ¦ λλ€. νΌλ° λ°°μ, 3κ³ λΆν° 2λ°°, μ μ μν, λ λ° μ²λ¦¬ λ°©μκΉμ§ μ λΆ μ€μ μΌλ‘ λΉ μ Έ μμ΅λλ€. λμ κΈ°λ³Έκ°λ§μΌλ‘λ λ°λ‘ μμν μ μκ² μ€μ νλ©΄μ μ ν μμ΅λλ€. "νλ κ³μ°μ μ νμ±λ³΄λ€ ν©μ κ°λ₯μ±μ΄ μ€μνλ€"λ μ μ μ λλ€.
κ° λΌμ΄λλ κ³μ°λ κΈμ‘ λ³νλ(deltas)μ μκΈ° μμ μΊμλ‘ λ€κ³ μμ΅λλ€.
μμ‘μ λΌμ΄λλ₯Ό λ§€λ² μ μ΄μ ꡬνκ³ , λ°λ‘ μ μ₯νμ§ μμ΅λλ€.
export function balances(rounds, n) {
const b = new Array(n).fill(0);
for (const r of rounds) r.deltas.forEach((v, i) => (b[i] += v));
return b;
}λλΆμ μΈ κ°μ§κ° 곡μ§λ‘ λ°λΌμ΅λλ€.
- λμ€μ λ£° μ€μ μ λ°κΏλ μ΄λ―Έ λλ νμ κΈμ‘μ΄ νλ€λ¦¬μ§ μλλ€
- μμ‘μ΄ λ³λ μνκ° μλλ―λ‘ μν λΆμΌμΉκ° μμ² λ΄μλλ€
- μ€νμ·¨μκ°
rounds.pop()ν μ€λ‘ λλλ€
κΈ°λ³Έμ 3λͺ
μ΄μ§λ§ λμ΄ λ§κ³ λ₯Ό μ³λ, λ€μ―μ΄ λλ¬μμλ λ©λλ€. κ³μ° μ½λλ μΈμμ μΈμλ‘ λ°κ³ ,
balances(rounds, n) μ n μλ κΈ°λ³Έκ°μ λμ§ μμ΅λλ€ β λΉ λ¨λ Έμ λ μ‘°μ©ν 3μΈμΌλ‘ λλ κ²λ³΄λ€
κ·Έ μ리μμ ν°μ§λ νΈμ΄ λ«κΈ° λλ¬Έμ
λλ€.
κ³ μ€ν±μ 4μΈλΆν° ν λͺ
μ΄ κ΄μ νκ³ μ¬λ―λ‘, λΌμ΄λμ sitout μ λ¬μ κ·Έ μ¬λμ 0μ μ²λ¦¬ν©λλ€.
κΈ°λ³Έμ μ μ μ°Έμ¬λΌ νλ²ν νμ ν μλ μΈμμ΄ λͺμ΄λ κ·Έλλ‘μ
λλ€.
λͺ¨λ λΌμ΄λμ
deltasν©μ λ°λμ 0μ΄κ³ , κΈΈμ΄λ μΈμκ³Ό κ°λ€.
κ³μ° ν¨μλ λ°ν μ§μ μ μ΄κ±Έ κ²μ¬νκ³ , μ΄κΈλλ©΄ μ μ₯νμ§ μκ³ μλ¬λ₯Ό λμ§λλ€. λͺ¨λ κΈμ‘μ μ μ(μ λ¨μ) λ‘λ§ λ€λ£Ήλλ€ β λΆλμμμ κΈμ§.
μλμ μΌλ‘ μ무κ²λ μ μλλ€.
| νλͺ© | κ²°μ | μ΄μ |
|---|---|---|
| νλ μμν¬ | μμ (Vanilla JS + ES Modules) | νλ©΄ 5κ°μ§λ¦¬ μ±μ λ°νμμ΄ νμ μλ€ |
| λΉλ | μμ | npx serve λ‘ λ°λ‘ λ¨κ³ , μ μ νΈμ€ν
μ κ·Έλλ‘ μ¬λΌκ°λ€ |
| μν μ μ₯ | localStorage |
μλ²Β·λ‘κ·ΈμΈΒ·κ³μ μ λΆ μμ |
| κ·Έλν | μΈλΌμΈ SVG μ§μ | κΊΎμμ λͺ κ°μ μ°¨νΈ λΌμ΄λΈλ¬λ¦¬ 200KBλ κ³Όνλ€ |
| ν μ€νΈ | node --test |
κ³μ°Β·μ μ₯Β·κ·Έλν 108κ°. DOM ν μ€νΈλ μκ³ νλ©΄μ λμΌλ‘ λ³Έλ€ |
| κΈκΌ΄ | νμ λ§ μΉν°νΈ | λ³Έλ¬Έμ μμ€ν νκΈ β μ€νλΌμΈμμ λ¨Όμ λ μΌ νλ€ |
| PWA | manifest.json + sw.js |
ννλ©΄ μΆκ° Β· μ€νλΌμΈ λμ |
| λ°°ν¬ | Cloudflare Workers μ μ μμ° | wrangler deploy ν μ€. Worker μ½λλ ν μ€λ μλ€ |
pandon-settle/
βββ SPEC.md β κΈ°λ₯ λͺ
μΈ
βββ docs/superpowers/specs/ β μν€ν
μ²Β·λ°°ν¬ μ€κ³μ
βββ docs/design/ β νλ©΄ μμΒ·λμμΈ μμ€ν
(.dc.html)
βββ wrangler.jsonc β Cloudflare λ°°ν¬ νμ
βββ public/ β μ΄ ν΄λλ§ λ°°ν¬λλ€ (μ 체 120KB)
β βββ index.html
β βββ styles.css β ν ν°κ³Ό μ»΄ν¬λνΈ
β βββ manifest.json
β βββ sw.js β μ€νλΌμΈ. μ±μ network-first, κΈκΌ΄μ cache-first
β βββ icon.svg Β· icon-{180,192,512,maskable}.png
β βββ src/
β βββ rules.js β κ²μλ³ μ μβκΈμ‘ (μμ ν¨μ)
β βββ settle.js β μ‘κΈ μ μ° (μμ ν¨μ)
β βββ store.js β μν + localStorage. μ μ₯μλ₯Ό μ£Όμ
λ°λλ€
β βββ chart.js β SVG λμ κ·Έλν (μμ ν¨μ)
β βββ ui.js β νλ©΄ λ λλ§ Β· μ΄λ²€νΈ
βββ test/ β λ°°ν¬λμ§ μμ
βββ helpers.js β λͺ¨λ μΌμ΄μ€μ 걸리λ κ³΅ν΅ λΆλ³μ
βββ settle.test.js Β· rules.test.js Β· store.test.js Β· chart.test.js
rules.js Β· settle.js Β· chart.js λ DOMμ μ λ μ°Έμ‘°νμ§ μμ΅λλ€. μμ ν¨μλ§ export ν©λλ€.
store.js λ μ μ₯μλ₯Ό μΈμλ‘ λ°μΌλ―λ‘(createStore({ storage })) λΈλΌμ°μ μμ΄λ
μ μ₯ μ€ν¨Β·λ°μ΄ν° μμ κ°μ κ²½λ‘λ₯Ό ν
μ€νΈν μ μμ΅λλ€.
| π΄ κ³ μ€ν± | π νλΌ | |
|---|---|---|
|
μΉμκ° λλ¨Έμ§ μ°Έμ¬μ κ°κ°μκ² λ°λ ꡬ쑰
|
simple β λ΄ μ¬λκ³Ό κΈμ‘λ§ λ£μΌλ©΄ λλ¨Έμ§κ° λλ μ λΆλ΄ν©λλ€. λ¨λ 1μμ κΈ°λ³Έμ μΌλ‘ λ΄ μ¬λμ΄ λ λ°μ΅λλ€(μ€μ μΌλ‘ λ°κΏ μ μμ).
|
ν¨μκ° μμ λ¨μ μΉ΄λ μ μλ§νΌ μ§λΆ
μμ¬μ μκ° 20~80κΉμ§ λμμ μ λΉ κΈμ‘μ κ²μλ³λ‘ λΆλ¦¬ν©λλ€ (κΈ°λ³Έ 10μ). |
μ¬νΈκ° 7μ μ 2κ³ λ‘ λ¬κ³ , μ² μκ° νΌλ°
곡ν΅μ μ = 7μ + 2κ³ = 9μ
μ² μ 9 Γ 2(νΌλ°) Γ 100 = 1,800μ μ§λΆ
μν¬ 9 Γ Γ 100 = 900μ μ§λΆ
μ¬νΈ = +2,700μ νλ
β deltas: [+2700, -1800, -900] (ν©κ³ 0 β
)
μμ‘ ν©μ΄ 0μ΄λ―λ‘ κ·Έλ¦¬λ λ§€μΉμ ν λ² λ λλ§λ€ μ΅μ ν μ¬λμ μμ§μν΅λλ€.
export function settle(balances) {
const plus = balances.map((v,i)=>({i,v})).filter(x=>x.v>0).sort((a,b)=>b.v-a.v);
const minus = balances.map((v,i)=>({i,v})).filter(x=>x.v<0).sort((a,b)=>a.v-b.v);
const out = [];
let p = 0, m = 0;
while (p < plus.length && m < minus.length) {
const amt = Math.min(plus[p].v, -minus[m].v);
out.push({ from: minus[m].i, to: plus[p].i, amount: amt });
plus[p].v -= amt; minus[m].v += amt;
if (plus[p].v === 0) p++;
if (minus[m].v === 0) m++;
}
return out; // length <= μΈμ - 1
}| μμ‘ | κ²°κ³Ό μ‘κΈ |
|---|---|
[+400, +1150, -1550] |
μν¬βμ² μ 1,150 / μν¬βμ¬νΈ 400 |
[+3000, -3000, 0] |
μ² μβμ¬νΈ 3,000 (1건) |
[+3000, -3000] |
2μΈμ μΈμ λ 1건 |
[+5000, +2000, -1000, -2000, -4000] |
5μΈμ 4건 (= μΈμβ1) |
[0, 0, 0] |
μμ |
3λͺ μ΄νμμλ μ΄κ² κ³§ μ΅μ 건μμ λλ€. 4λͺ μ΄μμμ λ μ€μ΄λ €λ©΄ μμ‘μ΄ λ± λ§μλ¨μ΄μ§λ μ‘°ν©μ λ€μ ΈμΌ νλλ°, κ·Έ μ΅μ νλ νμ§ μμ΅λλ€ β "ν° μ¬λλΆν° ν° μ¬λμκ²" κ° μ μ리μμ μ€λͺ νκΈ° κ°μ₯ μ¬μ΄ μμμ΄κ³ , μ΄ μ±μ μ νμ±λ³΄λ€ ν©μ κ°λ₯μ±μ λ¨Όμ λ΄ λλ€.
μ μ°μ΄ λλλ©΄ μ΄λ° ν
μ€νΈλ₯Ό 곡μ ν μ μμ΅λλ€ (navigator.share λλ ν΄λ¦½λ³΄λ 볡μ¬).
π΄πͺ νλμ μ° Β· 8/24 Β· κ³ μ€ν± 24ν
π€ μ¬νΈ +12,400
π μ² μ +3,100
π« μν¬ -15,500
πΈ μ μ°
μν¬ β μ¬νΈ 12,400μ
μν¬ β μ² μ 3,100μ
μμ‘μ λ°λΌ μ΄λͺ¨μ§κ° μλμΌλ‘ λΆμ΅λλ€ β 1λ± π€ / μν μ΄λ π / λ³Έμ π / μν μμ€ π₯ /
κΌ΄μ° π« μλ π μ€λμ νΈκ΅¬ μκ΄μ΄ μμμ§λλ€. μ§μ νμμ λ°°μλ₯Ό λ§μμΌλ©΄ π κ° λ°λΌλΆμ΅λλ€.
μ¬κΈ°μ μ¬λλ§λ€ ν μ€μ© κ·Έλ €μ§λ SVG λμ κ·Έλνμ μ΅κ³ ν ν / μΉλ₯ / νΌλ° μ΅λ€ κ°μ μ€λμ κΈ°λ‘μ΄ ν¨κ» λμ΅λλ€.
μ³λ³΄κ³ μ νλ €κ³ μΌλΆλ¬ λ¨κ²¨ λ κ²λ€μ
λλ€. μμΈν λ°°κ²½μ SPEC.md 12μ₯μ μμ΅λλ€.
- ν¬μ»€λ₯Ό μ€μ λ‘ μ΄λ»κ² μΉλκ°. μΉ©μ μ΄λ€λ©΄ νλ§λ€ λ£μ κ² μλλΌ "λλ λ μ΅μ’
μΉ© κ°μλ§"μ΄
μλμ μΌλ‘ νΈνκ³ , κ·Έλ¬λ©΄ ν¬μ»€ νλ©΄μ΄ μμ ν λ¬λΌμ§λλ€. κ·ΈλκΉμ§
simpleλͺ¨λλ§ μλλ€. - κ³ λ°μΌ λ κ°μΈ λ°°μ. μ§κΈμ κ³ λ°μ λ³ΈμΈ λ°°μλ§ μ μ©ν©λλ€.
- 5μΈ κ³ μ€ν±μ μ λ§ κ·Έλ κ² μΉλκ°. μ ν΅ λ£°μ΄λΌλ©΄ 4μΈλΆν° ν λͺ
μ΄ κ΄μ νκ³ μ¬λ―λ‘
5μΈμ λ λͺ
μ΄ μ¬λ μ
μ
λλ€.
sitoutμ΄ μ¬λ¬ λͺ μ λ°κΈ΄ νμ§λ§ νμΈλ λ°λ μμ΅λλ€. - ν λμ€ μΈμ λ³κ²½. λ¦κ² μ¨ μΉκ΅¬κ° λΌλ κ²½μ°. μ§κΈμ νμ΄ μμλλ©΄ μΈμ κ³ μ μ λλ€.
μ€μ ν°μμ νμΈ β ν νλ©΄μ μΆκ° β λΉνκΈ° λͺ¨λ β λ¨λκ°. λ°μ€ν¬ν± λΈλΌμ°μ μμλ μλ²λ₯Ό λ΄λ¦° μ±λ‘ λ¨λ κ²κΉμ§ νμΈνμ΅λλ€.
λͺ μΈ 13μ₯μ λͺ» λ°μ λ κΈμ§ μ¬νμ λλ€.
- β μλ² Β· λ‘κ·ΈμΈ Β· νμκ°μ β μ΄ μ±μ μ‘΄μ¬ μ΄μ κ° μ¬λΌμ§λλ€
- β μ°¨νΈ λΌμ΄λΈλ¬λ¦¬ λμ β κΊΎμμ λͺ κ°μ λ²λ€ 200KBλ κ³Όν©λλ€
- β κ³μ° κ²°κ³Όλ₯Ό μμ‘ λ³μμ λμ μ μ₯ β λ°λμ
roundsμμ λ§€λ² κ³μ°ν©λλ€ - β λ°°μ κ·μΉ νλμ½λ© β μ λΆ
rulesμ€μ μ κ±°μ³μΌ ν©λλ€ - β μ λ ₯ λ¨κ³ μΆκ° β μ κΈ°λ₯μ΄ ν μλ₯Ό λλ¦°λ€λ©΄ κΈ°λ³Έκ°μ λ€μ μ€κ³ν©λλ€
- β κ³μ° μ½λμ μΈμ μμ
3β μΈμμ μΈμ λplayers.lengthμμ νλ¬ λ€μ΄μ΅λλ€