|
2026-09-02 05:14
조회: 342
추천: 0
저장용 보2지마<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>상급 아비도스 제작 계산기</title> <style> /* [스타일 정의] 다크 테마 및 고정 레이아웃 */ :root { --bg-color: #2d2d2d; --tab-bg: #3c3c3c; --input-bg: #505050; --text-color: #ffffff; --label-color: #d3d3d3; --btn-color: #007acc; --btn-hover: #005f9e; --result-ok: #ffffff; --result-no: #fa8072; } body { background-color: #1e1e1e; color: var(--text-color); font-family: 'Malgun Gothic', 'Segoe UI', sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } /* 메인 컨테이너 (창 크기 고정) */ .container { width: 500px; height: 600px; /* 고정 높이 */ background-color: var(--bg-color); border: 1px solid #444; border-radius: 12px; box-shadow: 0 10px 20px rgba(0,0,0,0.5); display: flex; flex-direction: column; overflow: hidden; /* 크기 고정 */ } /* 탭 영역 */ .tabs { display: flex; height: 35px; background-color: #1e1e1e; flex-shrink: 0; } .tab { flex: 1; background-color: var(--bg-color); color: #888; border: none; outline: none; cursor: pointer; font-size: 14px; transition: background 0.2s; border-right: 1px solid #444; border-bottom: 1px solid #444; padding: 0; } .tab.active { background-color: var(--tab-bg); color: #fff; border-bottom: none; font-weight: bold; } .tab:last-child { border-right: none; } /* 입력 영역 (스크롤 가능) */ .input-area { flex: 1; /* 남은 공간 차지 */ padding: 20px; background-color: var(--tab-bg); overflow-y: auto; display: flex; flex-direction: column; gap: 15px; } /* 입력 필드 레이아웃 (Grid 사용) */ .input-group { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 10px; } .input-group label { color: var(--label-color); font-size: 14px; text-align: left; } .input-group input { background-color: var(--input-bg); border: 1px solid #666; border-radius: 12px; color: white; padding: 8px; font-size: 14px; outline: none; width: 100%; box-sizing: border-box; /* 패딩 포함 크기 계산 */ } .input-group input:focus { border-color: var(--btn-color); } /* 버튼 영역 */ .btn-area { padding: 25px 0; flex-shrink: 0; display: flex; justify-content: center; } .calculate-btn { width: 40%; height: 50px; background-color: var(--btn-color); color: white; border: none; border-radius: 12px; font-size: 18px; font-weight: bold; cursor: pointer; transition: background 0.2s; } .calculate-btn:hover { background-color: var(--btn-hover); } /* 결과 영역 (완벽한 중앙 정렬) */ .result-area { flex: 1; /* 하단 남은 공간 채우기 */ display: flex; justify-content: center; /* 가로 중앙 */ align-items: center; /* 세로 중앙 */ text-align: center; padding: 20px; white-space: pre-line; /* 줄바꿈 허용 */ font-size: 15px; line-height: 1.6; background-color: var(--bg-color); overflow-y: auto; } .result-text { color: #ffffff; } .result-ok { color: var(--result-ok); } .result-no { color: var(--result-no); } </style> </head> <body> <div class="container"> <div class="tabs" id="tabContainer"> </div> <div class="input-area" id="inputContainer"> </div> <div class="btn-area"> <button class="calculate-btn" onclick="calculate()">계산하기 (Calculate)</button> </div> <div class="result-area"> <div id="resultText" class="result-text">재료를 입력하고 계산 버튼을 눌러주세요.</div> </div> </div> <script> // ========================================== // [1] 데이터 및 설정 // ========================================== const MATERIAL_TYPES = { WL: { name: "벌목", A: "원목", B: "부드러운 목재", S: null, C: "아비도스 원목", P: "벌목의 흔적" }, AR: { name: "고고학", A: "오래된 고대 유물", B: "은은한 고대 유물", S: null, C: "아비도스 고대 유물", P: "고고학의 흔적" }, FS: { name: "낚시", A: "싱싱한 피라미", B: "튼튼한 붕어", S: null, C: "아비도스 금빛 잉어", P: "낚시의 흔적" }, MN: { name: "채광", A: "투박한 철광석", B: "천연 보석", S: null, C: "아비도스 원석", P: "채광의 흔적" }, HR: { name: "수렵", A: "생고기", B: "고급 생고기", S: null, C: "아비도스 농장 생고기", P: "수렵의 흔적" }, CL: { name: "채집", A: "소박한 꽃잎", B: "귀여운 꽃잎", S: null, C: "아비도스 꽃잎", P: "채집의 흔적" } }; const inputState = { WL: { A: "", B: "", S: "", C: "", P: "" }, AR: { A: "", B: "", S: "", C: "", P: "" }, FS: { A: "", B: "", S: "", C: "", P: "" }, MN: { A: "", B: "", S: "", C: "", P: "" }, HR: { A: "", B: "", S: "", C: "", P: "" }, CL: { A: "", B: "", S: "", C: "", P: "" } }; let currentType = 'WL'; // 교환비 (입력 단위 -> 출력 단위) const RATES = { StoA: { input: 5n, output: 50n }, BtoA: { input: 25n, output: 50n }, AtoP: { input: 100n, output: 80n }, // 100개 넣어야 80개 나옴 BtoP: { input: 50n, output: 80n }, PtoC: { input: 100n, output: 10n } // 100개 넣어야 10개 나옴 }; const RECIPE = { A: 112n, B: 59n, C: 43n }; // ========================================== // [2] 핵심 로직 (이진 탐색 + 묶음 단위 적용) // ========================================== function runAlgorithm(type, inputs) { const canExchangeSpecial = (type === 'WL' || type === 'MN'); // BigInt 변환 (빈 문자열은 0n으로 처리) const res = { A: BigInt(inputs.A || 0), B: BigInt(inputs.B || 0), S: BigInt(inputs.S || 0), C: BigInt(inputs.C || 0), P: BigInt(inputs.P || 0) }; let low = 0n; // 충분히 큰 상한값 설정 let maxA = res.A + (res.B * 2n) + (res.C * 20n); let high = (maxA / RECIPE.A) + 2000000000n; let bestOutput = 0n; let bestLog = {}; // 이진 탐색 시작 while (low <= high) { let mid = low + (high - low) / 2n; if (mid < 0n) break; const check = canProduce(mid, { ...res }, canExchangeSpecial); if (check.possible) { bestOutput = mid; bestLog = check.log; low = mid + 1n; // 더 큰 값을 찾아봄 } else { high = mid - 1n; // 불가능하므로 범위를 줄임 } } return { output: bestOutput, logs: bestLog }; } // 100개 단위 묶음(Batch) 교환 규칙을 엄격하게 적용한 검사 함수 function canProduce(targetCount, inputRes, canExchangeSpecial) { let log = { StoA: 0n, BtoA: 0n, AtoP: 0n, BtoP: 0n, PtoC: 0n }; if (targetCount === 0n) return { possible: true, log }; let res = { ...inputRes }; // 자원 복사본 사용 // 1. S -> A 변환 (5개 단위) if (canExchangeSpecial && res.S > 0n) { let batches = res.S / RATES.StoA.input; // 몫만 취함 (자투리 버림) log.StoA = batches; res.A += batches * RATES.StoA.output; } // 2. 목표 제작을 위한 필요량 계산 let reqA = targetCount * RECIPE.A; let reqB = targetCount * RECIPE.B; let reqC = targetCount * RECIPE.C; // [검증 1] B 재료 절대량 부족 시 불가 if (res.B < reqB) return { possible: false, log }; // [검증 2] 부족한 C를 채우기 위한 가루(P) 계산 (100개 단위 묶음 적용) let missingC = 0n; if (res.C < reqC) { missingC = reqC - res.C; } let neededP = 0n; if (missingC > 0n) { // C 10개를 얻기 위해 P 100개가 필요함 let outputPerBatch = RATES.PtoC.output; // 10 let inputPerBatch = RATES.PtoC.input; // 100 // 필요 묶음 수 올림 계산: (부족분 + 9) / 10 let batchesC = (missingC + outputPerBatch - 1n) / outputPerBatch; neededP = batchesC * inputPerBatch; // 실제 필요한 가루 양 (100의 배수) log.PtoC = batchesC; } // 보유 가루(P) 차감 if (res.P >= neededP) { res.P -= neededP; neededP = 0n; } else { neededP -= res.P; res.P = 0n; } // [검증 3] 남은 재료를 모두 A로 환산하여 최종 비교 (묶음 단위 적용) // 3-1. 잉여 B -> A 환산 (25개 단위) let totalAvailableA = res.A; // 3-2. 부족한 가루를 A 또는 남는 B로 제작 let remainingP = neededP; // 먼저 남는 B를 가루로 교환 let surplusB = res.B - reqB; if (remainingP > 0n && surplusB >= RATES.BtoP.input) { let possibleBatches = surplusB / RATES.BtoP.input; let neededBatches = (remainingP + RATES.BtoP.output - 1n) / RATES.BtoP.output; let batchesBtoP = possibleBatches < neededBatches ? possibleBatches : neededBatches; log.BtoP = batchesBtoP; let producedP = batchesBtoP * RATES.BtoP.output; remainingP -= producedP; if (remainingP < 0n) remainingP = 0n; } // 아직 부족하면 A를 가루로 교환 let aForP = 0n; if (remainingP > 0n) { let batchesAtoP = (remainingP + RATES.AtoP.output - 1n) / RATES.AtoP.output; aForP = batchesAtoP * RATES.AtoP.input; log.AtoP = batchesAtoP; } // 제작용 철광석 + 가루 제작용 철광석 let totalReqA = reqA + aForP; if (res.A >= totalReqA) { return { possible: true, log }; } return { possible: false, log }; return { possible: false, log }; } // ========================================== // [3] UI 컨트롤 및 이벤트 // ========================================== function init() { renderTabs(); renderInputs(); } function renderTabs() { const tabContainer = document.getElementById('tabContainer'); tabContainer.innerHTML = ''; Object.keys(MATERIAL_TYPES).forEach(key => { const btn = document.createElement('button'); btn.className = `tab ${key === currentType ? 'active' : ''}`; btn.innerText = MATERIAL_TYPES[key].name; btn.onclick = () => { currentType = key; renderTabs(); renderInputs(); resetResultBox(); }; tabContainer.appendChild(btn); }); } function renderInputs() { const container = document.getElementById('inputContainer'); container.innerHTML = ''; const info = MATERIAL_TYPES[currentType]; const fields = ['A', 'B', 'S', 'C', 'P']; fields.forEach(fieldKey => { const labelText = info[fieldKey]; if (!labelText) return; const group = document.createElement('div'); group.className = 'input-group'; const label = document.createElement('label'); label.innerText = labelText; const input = document.createElement('input'); input.type = 'text'; input.value = inputState[currentType][fieldKey]; input.placeholder = "0"; // 숫자만 입력되도록 처리 input.oninput = (e) => { e.target.value = e.target.value.replace(/[^0-9]/g, ''); inputState[currentType][fieldKey] = e.target.value; }; group.appendChild(label); group.appendChild(input); container.appendChild(group); }); } function resetResultBox() { const resultEl = document.getElementById('resultText'); resultEl.innerText = "재료를 입력하고 계산 버튼을 눌러주세요."; resultEl.className = "result-text"; } function calculate() { const inputs = inputState[currentType]; // 입력값이 하나도 없으면 계산하지 않음 if (Object.values(inputs).every(val => val === "" || val === "0")) { resetResultBox(); return; } const result = runAlgorithm(currentType, inputs); const info = MATERIAL_TYPES[currentType]; const resultEl = document.getElementById('resultText'); if (result.output === 0n) { resultEl.innerText = "제작 가능한 수량이 없습니다."; resultEl.className = "result-text result-no"; } else { resultEl.className = "result-text result-ok"; const fmt = (n) => n.toLocaleString(); let text = `[ ${info.name} 결과 ]<br><br>`; text += `총 제작 가능 횟수: ${fmt(result.output)}회<br><br>`; text += `< 최적 교환 경로 ><br>`; const logs = result.logs; // 교환 횟수(묶음 단위)를 표시 if (logs.AtoP > 0n) text += `<span style="color:#999999">${info.A}</span> -> <span style="color:#D9A441">${info.P}</span> : ${fmt(logs.AtoP)}회 교환<br>`; if (logs.BtoP > 0n) text += `<span style="color:#4dac00">${info.B}</span> -> <span style="color:#D9A441">${info.P}</span> : ${fmt(logs.BtoP)}회 교환<br>`; if (logs.PtoC > 0n) text += `<span style="color:#D9A441">${info.P}</span> -> <span style="color:#006dc6">${info.C}</span> : ${fmt(logs.PtoC)}회 교환<br>`; resultEl.innerHTML = text; } } // 초기화 실행 init(); </script> </body> </html> <!-- Original: 상급 아비도스 계산기 Original author: @tuijblab-the-decoder Original: https://codepen.io/tuijblab-the-decoder/pen/azZpWrW -->
|





