Diff
checker
テキスト
テキスト
画像
ドキュメント
Excel
フォルダ
Legal
Enterprise
デスクトップ
料金
ログイン
Diffchecker デスクトップのダウンロード
テキスト比較
2 つのテキスト ファイルの違いを見つける
ツール
履歴
ライブエディター
未変更行を折りたたむ
折り返しなし
レイアウト
分割
統合
比較精度
スマート
単語
文字
シンタックスハイライト
構文を選択
無視
テキスト変換
最初の差分へ移動
入力を編集
Diffchecker Desktop
Diffcheckerを実行する最も安全な方法。Diffchecker Desktopアプリを入手:あなたの差分はコンピューターから出ることはありません!
Desktopを入手
abyssalfarm.html
作成日
16 時間前
差分は期限切れになりません
クリア
エクスポート
共有
説明
1 削除
行
合計
削除
文字
合計
削除
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
1 行
すべてコピー
929 追加
行
合計
追加
文字
合計
追加
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
929 行
すべてコピー
コピー
コピー済み
コピー
コピー済み
1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>ABYSSAL FARM — Void Breeder</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
:root {
--bg: #0a0a0f;
--panel: #12121a;
--panel2: #1a1a25;
--accent: #8b0000;
--accent2: #4a0080;
--accent3: #1a5c1a;
--text: #c0c0c0;
--text-dim: #555;
--border: #2a2a35;
--glow-red: 0 0 8px #8b0000;
--glow-purple: 0 0 8px #4a0080;
--glow-green: 0 0 8px #1a5c1a;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
margin: 0; padding: 0;
background: var(--bg);
color: var(--text);
font-family: 'VT323', monospace;
font-size: 18px;
overflow: hidden;
height: 100vh;
width: 100vw;
touch-action: manipulation;
}
/* CRT overlay */
body::before {
content: "";
position: fixed; top:0; left:0; right:0; bottom:0;
background: repeating-linear-gradient(
0deg,
rgba(0,0,0,0.15),
rgba(0,0,0,0.15) 1px,
transparent 1px,
transparent 2px
);
pointer-events: none;
z-index: 9999;
}
body::after {
content: "";
position: fixed; top:0; left:0; right:0; bottom:0;
background: radial-gradient(circle, transparent 60%, rgba(0,0,0,0.6) 100%);
pointer-events: none;
z-index: 9998;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 600px;
margin: 0 auto;
position: relative;
z-index: 1;
}
/* Header */
.header {
background: var(--panel);
border-bottom: 2px solid var(--border);
padding: 8px 12px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.header h1 {
margin: 0;
font-size: 1.4rem;
color: var(--accent);
text-shadow: var(--glow-red);
letter-spacing: 2px;
}
.resources {
display: flex;
gap: 12px;
font-size: 0.95rem;
}
.res-item {
display: flex;
align-items: center;
gap: 4px;
}
.res-icon {
width: 14px; height: 14px;
display: inline-block;
}
/* Tabs */
.tabs {
display: flex;
background: var(--panel);
border-bottom: 2px solid var(--border);
flex-shrink: 0;
}
.tab {
flex: 1;
padding: 10px;
text-align: center;
cursor: pointer;
border: none;
background: transparent;
color: var(--text-dim);
font-family: 'VT323', monospace;
font-size: 1rem;
border-bottom: 3px solid transparent;
transition: all 0.2s;
}
.tab.active {
color: var(--text);
border-bottom-color: var(--accent);
background: var(--panel2);
}
.tab:hover { color: var(--text); }
/* Content area */
.content {
flex: 1;
overflow-y: auto;
padding: 12px;
position: relative;
}
.content::-webkit-scrollbar { width: 6px; }
.content::-webkit-scrollbar-thumb { background: var(--accent); }
/* Monster Cards */
.monster-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
}
.monster-card {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 8px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
position: relative;
overflow: hidden;
}
.monster-card:hover {
border-color: var(--accent);
box-shadow: var(--glow-red);
}
.monster-card.selected {
border-color: var(--accent2);
box-shadow: var(--glow-purple);
}
.monster-canvas {
width: 64px; height: 64px;
image-rendering: pixelated;
margin: 0 auto 6px;
display: block;
}
.monster-name {
font-size: 0.9rem;
color: var(--accent);
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.monster-lvl {
font-size: 0.8rem;
color: var(--text-dim);
}
.monster-bar {
width: 100%; height: 4px;
background: #222;
margin-top: 4px;
border-radius: 2px;
overflow: hidden;
}
.monster-bar-fill {
height: 100%;
background: var(--accent);
transition: width 0.3s;
}
/* Hatch Button */
.hatch-area {
text-align: center;
padding: 20px;
margin-bottom: 16px;
}
.hatch-btn {
width: 120px; height: 120px;
border-radius: 50%;
border: 3px solid var(--accent);
background: var(--panel);
color: var(--accent);
font-family: 'VT323', monospace;
font-size: 1.2rem;
cursor: pointer;
position: relative;
overflow: hidden;
box-shadow: var(--glow-red);
transition: transform 0.1s;
display: inline-flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.hatch-btn:active { transform: scale(0.92); }
.hatch-btn .cost {
font-size: 0.8rem;
color: var(--text-dim);
margin-top: 4px;
}
.hatch-btn::before {
content: "";
position: absolute;
width: 80%; height: 80%;
border: 2px dashed var(--accent);
border-radius: 50%;
animation: spin 8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Upgrades / Automation */
.upgrade-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.upgrade-item {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: all 0.2s;
}
.upgrade-item:hover { border-color: var(--accent2); }
.upgrade-item.disabled {
opacity: 0.4;
cursor: not-allowed;
}
.upgrade-info { flex: 1; }
.upgrade-name { font-size: 1rem; color: var(--accent2); }
.upgrade-desc { font-size: 0.8rem; color: var(--text-dim); }
.upgrade-cost {
background: var(--panel2);
padding: 4px 10px;
border-radius: 4px;
border: 1px solid var(--border);
font-size: 0.9rem;
white-space: nowrap;
}
/* Farm Zones */
.zone-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.zone-card {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 12px;
cursor: pointer;
position: relative;
}
.zone-card:hover { border-color: var(--accent3); }
.zone-name { font-size: 1.1rem; color: var(--accent3); margin-bottom: 4px; }
.zone-desc { font-size: 0.85rem; color: var(--text-dim); }
.zone-reward { font-size: 0.9rem; color: var(--text); margin-top: 6px; }
.zone-danger {
position: absolute;
top: 8px; right: 8px;
font-size: 0.75rem;
color: var(--accent);
}
/* Detail Modal */
.modal-overlay {
position: fixed;
top:0; left:0; right:0; bottom:0;
background: rgba(0,0,0,0.85);
display: none;
align-items: center;
justify-content: center;
z-index: 10000;
padding: 20px;
}
.modal-overlay.active { display: flex; }
.modal {
background: var(--panel);
border: 2px solid var(--accent);
border-radius: 8px;
padding: 20px;
max-width: 400px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
position: relative;
}
.modal-close {
position: absolute;
top: 8px; right: 12px;
background: none;
border: none;
color: var(--text);
font-size: 1.5rem;
cursor: pointer;
font-family: 'VT323', monospace;
}
.modal-title {
font-size: 1.3rem;
color: var(--accent);
margin-bottom: 12px;
text-align: center;
}
.modal-canvas {
width: 96px; height: 96px;
image-rendering: pixelated;
margin: 0 auto 12px;
display: block;
}
.stat-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
border-bottom: 1px solid var(--border);
font-size: 0.95rem;
}
.action-btn {
width: 100%;
padding: 10px;
margin-top: 10px;
border: 2px solid var(--accent);
background: var(--panel2);
color: var(--text);
font-family: 'VT323', monospace;
font-size: 1rem;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
}
.action-btn:hover { background: var(--accent); color: #fff; }
.action-btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* Floating text */
.float-text {
position: absolute;
color: var(--accent);
font-size: 1rem;
pointer-events: none;
animation: floatUp 1s ease-out forwards;
z-index: 100;
}
@keyframes floatUp {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-40px); }
}
/* Log */
.log-area {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 8px;
height: 120px;
overflow-y: auto;
font-size: 0.85rem;
color: var(--text-dim);
}
.log-entry { margin-bottom: 2px; }
.log-entry.new { color: var(--text); }
/* Glitch effect on title */
.glitch {
animation: glitch 3s infinite;
}
@keyframes glitch {
0%, 90%, 100% { text-shadow: var(--glow-red); }
92% { text-shadow: 2px 0 #0ff, -2px 0 #f0f; }
94% { text-shadow: -2px 0 #0ff, 2px 0 #f0f; }
96% { text-shadow: var(--glow-red); }
}
/* Notification toast */
.toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
background: var(--panel);
border: 2px solid var(--accent);
padding: 10px 20px;
border-radius: 4px;
font-size: 1rem;
z-index: 10001;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
white-space: nowrap;
}
.toast.show { opacity: 1; }
/* Evolution tree */
.evo-tree {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin: 12px 0;
flex-wrap: wrap;
}
.evo-node {
width: 48px; height: 48px;
border: 2px solid var(--border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
color: var(--text-dim);
background: var(--panel2);
}
.evo-node.active {
border-color: var(--accent);
color: var(--accent);
box-shadow: var(--glow-red);
}
.evo-arrow { color: var(--text-dim); font-size: 1.2rem; }
/* Particle canvas */
#particleCanvas {
position: fixed;
top: 0; left: 0;
pointer-events: none;
z-index: 9997;
}
/* Responsive */
@media (max-width: 400px) {
.monster-grid { grid-template-columns: repeat(2, 1fr); }
.header h1 { font-size: 1.1rem; }
}
</style>
</head>
<body>
<canvas id="particleCanvas"></canvas>
<div id="app">
<div class="header">
<h1 class="glitch">ABYSSAL FARM</h1>
<div class="resources">
<div class="res-item"><span style="color:#8b0000">◆</span> <span id="resDread">0</span></div>
<div class="res-item"><span style="color:#4a0080">◆</span> <span id="resSouls">0</span></div>
<div class="res-item"><span style="color:#1a5c1a">◆</span> <span id="resShards">0</span></div>
</div>
</div>
<div class="tabs">
<button class="tab active" data-tab="hatch">HATCH</button>
<button class="tab" data-tab="monsters">MONSTERS</button>
<button class="tab" data-tab="upgrades">VOID SHOP</button>
<button class="tab" data-tab="zones">REALMS</button>
</div>
<div class="content" id="content">
<!-- Dynamic content -->
</div>
<div class="log-area" id="logArea">
<div class="log-entry">Welcome to the Abyss. Hatch your first abomination.</div>
</div>
</div>
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal" id="modal">
<button class="modal-close" onclick="closeModal()">×</button>
<div id="modalContent"></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
// ==================== GAME STATE ====================
const state = {
dread: 0,
souls: 0,
shards: 0,
totalDread: 0,
monsters: [],
nextId: 1,
selectedMonster: null,
tab: 'hatch',
upgrades: {
extractor: { level: 0, cost: 50, costScale: 1.6, name: "Void Extractor", desc: "Auto-generates +1 Dread/sec", effect: 1 },
harvester: { level: 0, cost: 200, costScale: 1.7, name: "Soul Harvester", desc: "Auto-generates +0.5 Souls/sec", effect: 0.5 },
incubator: { level: 0, cost: 500, costScale: 1.8, name: "Corrupt Incubator", desc: "Monsters hatch 1 tier higher base", effect: 1 },
amplifier: { level: 0, cost: 1000, costScale: 1.9, name: "Dread Amplifier", desc: "All monster production ×1.5", effect: 1.5 },
voidgate: { level: 0, cost: 5000, costScale: 2.0, name: "Void Gate", desc: "Unlocks Realm farming", effect: 1 },
mutagen: { level: 0, cost: 10000, costScale: 2.2, name: "Mutagen Vat", desc: "Evolution cost reduced by 10%", effect: 0.9 },
},
zones: {
crypt: { unlocked: true, name: "Forgotten Crypt", desc: "Ancient burial grounds teeming with weak spirits", dreadRate: 5, soulRate: 0, danger: 0.05, reward: "+5 Dread/sec" },
swamp: { unlocked: false, name: "Rotting Swamp", desc: "Bubbling miasma corrupts everything it touches", dreadRate: 15, soulRate: 1, danger: 0.1, reward: "+15 Dread, +1 Soul/sec" },
abyss: { unlocked: false, name: "The Abyss", desc: "Stare into the void. It stares back.", dreadRate: 50, soulRate: 5, danger: 0.2, reward: "+50 Dread, +5 Souls/sec" },
nullspace: { unlocked: false, name: "Null Space", desc: "Reality unravels. Only the strongest survive.", dreadRate: 200, soulRate: 20, danger: 0.35, reward: "+200 Dread, +20 Souls/sec" },
},
activeZone: null,
zoneProgress: 0,
zoneHealth: 100,
};
// ==================== MONSTER DATA ====================
const MONSTER_TYPES = [
// Tier 0 - Larvae
{ tier: 0, name: "Grubspawn", hp: 10, atk: 1, def: 0, spd: 1, prod: 0.5, evo: "Mawling", palette: [[0,80,0],[40,120,40],[20,60,20]] },
{ tier: 0, name: "Boneling", hp: 8, atk: 2, def: 0, spd: 2, prod: 0.4, evo: "Ribcage", palette: [[200,200,200],[150,150,150],[100,100,100]] },
{ tier: 0, name: "Sludge", hp: 15, atk: 1, def: 1, spd: 0, prod: 0.6, evo: "Muckfiend", palette: [[60,40,20],[100,80,40],[80,60,30]] },
// Tier 1 - Juveniles
{ tier: 1, name: "Mawling", hp: 30, atk: 4, def: 1, spd: 3, prod: 1.5, evo: "Goremaw", palette: [[120,0,0],[180,20,20],[80,0,0]] },
{ tier: 1, name: "Ribcage", hp: 25, atk: 5, def: 2, spd: 4, prod: 1.2, evo: "Ossuary", palette: [[220,220,220],[180,180,180],[120,120,120]] },
{ tier: 1, name: "Muckfiend", hp: 40, atk: 3, def: 3, spd: 1, prod: 1.8, evo: "Rotlord", palette: [[80,60,30],[120,100,50],[60,40,10]] },
{ tier: 1, name: "Shadelet", hp: 20, atk: 6, def: 0, spd: 5, prod: 1.0, evo: "Dreadwraith", palette: [[40,0,60],[80,20,100],[20,0,40]] },
// Tier 2 - Adults
{ tier: 2, name: "Goremaw", hp: 80, atk: 10, def: 3, spd: 5, prod: 4.0, evo: "Fleshbeast", palette: [[160,0,0],[220,40,40],[100,0,0]] },
{ tier: 2, name: "Ossuary", hp: 70, atk: 12, def: 5, spd: 6, prod: 3.5, evo: "Catacomb", palette: [[240,240,240],[200,200,200],[140,140,140]] },
{ tier: 2, name: "Rotlord", hp: 100, atk: 8, def: 6, spd: 2, prod: 5.0, evo: "Plaguefather", palette: [[100,80,40],[140,120,60],[80,60,20]] },
{ tier: 2, name: "Dreadwraith", hp: 60, atk: 15, def: 1, spd: 10, prod: 3.0, evo: "Voidreaper", palette: [[60,0,80],[100,30,120],[40,0,60]] },
// Tier 3 - Elders
{ tier: 3, name: "Fleshbeast", hp: 200, atk: 25, def: 8, spd: 6, prod: 12.0, evo: "Abomination", palette: [[180,20,20],[240,60,60],[120,0,0]] },
{ tier: 3, name: "Catacomb", hp: 180, atk: 28, def: 12, spd: 8, prod: 10.0, evo: "Gravemind", palette: [[255,255,255],[220,220,220],[160,160,160]] },
{ tier: 3, name: "Plaguefather", hp: 250, atk: 20, def: 15, spd: 3, prod: 15.0, evo: "Pestilence", palette: [[120,100,50],[160,140,70],[100,80,30]] },
{ tier: 3, name: "Voidreaper", hp: 150, atk: 35, def: 4, spd: 15, prod: 9.0, evo: "Null Emperor", palette: [[80,0,100],[120,40,140],[60,0,80]] },
// Tier 4 - Apex
{ tier: 4, name: "Abomination", hp: 600, atk: 60, def: 20, spd: 8, prod: 40.0, evo: null, palette: [[200,0,0],[255,50,50],[150,0,0]] },
{ tier: 4, name: "Gravemind", hp: 550, atk: 65, def: 25, spd: 10, prod: 35.0, evo: null, palette: [[255,255,255],[230,230,230],[180,180,180]] },
{ tier: 4, name: "Pestilence", hp: 700, atk: 50, def: 30, spd: 4, prod: 50.0, evo: null, palette: [[140,120,60],[180,160,80],[120,100,40]] },
{ tier: 4, name: "Null Emperor", hp: 500, atk: 80, def: 10, spd: 20, prod: 30.0, evo: null, palette: [[100,0,120],[140,50,160],[80,0,100]] },
];
const HATCH_COST_BASE = 10;
const HATCH_COST_SCALE = 1.15;
let hatchCount = 0;
// ==================== PIXEL ART GENERATOR ====================
function generateMonsterSprite(type, size = 64) {
const canvas = document.createElement('canvas');
canvas.width = size; canvas.height = size;
const ctx = canvas.getContext('2d');
const seed = type.name.split('').reduce((a,b)=>a+b.charCodeAt(0),0);
const rng = mulberry32(seed + Date.now() % 10000);
const palette = type.palette;
const px = size / 16;
// Background glow
ctx.fillStyle = `rgba(${palette[0][0]},${palette[0][1]},${palette[0][2]},0.1)`;
ctx.fillRect(0,0,size,size);
// Body shape based on tier
const bodyColor = `rgb(${palette[0][0]},${palette[0][1]},${palette[0][2]})`;
const detailColor = `rgb(${palette[1][0]},${palette[1][1]},${palette[1][2]})`;
const darkColor = `rgb(${palette[2][0]},${palette[2][1]},${palette[2][2]})`;
ctx.fillStyle = bodyColor;
// Procedural body
const cx = 8, cy = 8;
const bodyW = 6 + type.tier + Math.floor(rng()*4);
const bodyH = 6 + type.tier + Math.floor(rng()*4);
for(let y=0; y<16; y++) {
for(let x=0; x<16; x++) {
const dx = x - cx;
const dy = y - cy;
const dist = Math.sqrt(dx*dx + dy*dy);
const noise = rng();
if (dist < bodyW/2 + noise) {
// Core body
ctx.fillStyle = bodyColor;
ctx.fillRect(x*px, y*px, px, px);
} else if (dist < bodyW/2 + 2 && noise > 0.4) {
// Details / limbs
ctx.fillStyle = detailColor;
ctx.fillRect(x*px, y*px, px, px);
} else if (dist < bodyW/2 + 3 && noise > 0.7) {
// Dark accents
ctx.fillStyle = darkColor;
ctx.fillRect(x*px, y*px, px, px);
}
}
}
// Eyes
const eyeY = cy - 1 + Math.floor(rng()*3);
const eyeX1 = cx - 2 - Math.floor(rng()*2);
const eyeX2 = cx + 2 + Math.floor(rng()*2);
ctx.fillStyle = '#ff0000';
if (rng() > 0.3) ctx.fillRect(eyeX1*px, eyeY*px, px, px);
if (rng() > 0.3) ctx.fillRect(eyeX2*px, eyeY*px, px, px);
// Mouth
if (type.tier >= 1) {
ctx.fillStyle = darkColor;
for(let i=-2; i<=2; i++) {
if (rng() > 0.2) ctx.fillRect((cx+i)*px, (cy+3)*px, px, px);
}
}
// Horns / spikes for higher tiers
if (type.tier >= 2) {
ctx.fillStyle = detailColor;
ctx.fillRect((cx-3)*px, (cy-4)*px, px, px*2);
ctx.fillRect((cx+3)*px, (cy-4)*px, px, px*2);
}
if (type.tier >= 3) {
ctx.fillStyle = '#fff';
ctx.fillRect((cx-1)*px, (cy-5)*px, px, px*2);
ctx.fillRect((cx+1)*px, (cy-5)*px, px, px*2);
}
return canvas;
}
function mulberry32(a) {
return function() {
let t = a += 0x6D2B79F5;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
}
// ==================== CORE FUNCTIONS ====================
function getHatchCost() {
return Math.floor(HATCH_COST_BASE * Math.pow(HATCH_COST_SCALE, hatchCount));
}
function hatchMonster() {
const cost = getHatchCost();
if (state.dread < cost) {
showToast("Not enough Dread!");
return;
}
state.dread -= cost;
hatchCount++;
const bonus = state.upgrades.incubator.level;
const available = MONSTER_TYPES.filter(m => m.tier <= Math.min(bonus, 4));
const type = available[Math.floor(Math.random() * available.length)];
const monster = {
id: state.nextId++,
type: type,
level: 1,
xp: 0,
xpToLevel: 10,
hp: type.hp,
maxHp: type.hp,
atk: type.atk,
def: type.def,
spd: type.spd,
prod: type.prod,
sprite: null,
};
monster.sprite = generateMonsterSprite(type);
state.monsters.push(monster);
log(`Hatched ${type.name}!`);
showToast(`Hatched: ${type.name}`);
spawnParticles(window.innerWidth/2, window.innerHeight/2, type.palette[0]);
render();
}
function levelUpMonster(m) {
const cost = Math.floor(m.xpToLevel * 0.5);
if (state.dread < cost) {
showToast("Not enough Dread!");
return;
}
state.dread -= cost;
m.level++;
m.xp = 0;
m.xpToLevel = Math.floor(m.xpToLevel * 1.5);
m.maxHp = Math.floor(m.maxHp * 1.2);
m.hp = m.maxHp;
m.atk = Math.floor(m.atk * 1.15);
m.def = Math.floor(m.def * 1.1);
m.spd = Math.floor(m.spd * 1.1);
m.prod = m.prod * 1.1;
log(`${m.type.name} reached level ${m.level}!`);
showToast(`${m.type.name} Lv.${m.level}`);
render();
}
function evolveMonster(m) {
if (!m.type.evo) {
showToast("Cannot evolve further!");
return;
}
const evoName = m.type.evo;
const evoType = MONSTER_TYPES.find(t => t.name === evoName);
if (!evoType) return;
let cost = Math.floor(100 * Math.pow(2, evoType.tier) * Math.pow(state.upgrades.mutagen ? 0.9 : 1, state.upgrades.mutagen.level));
if (state.souls < cost) {
showToast(`Need ${cost} Souls!`);
return;
}
state.souls -= cost;
m.type = evoType;
m.level = 1;
m.xp = 0;
m.xpToLevel = 20;
m.maxHp = evoType.hp;
m.hp = evoType.hp;
m.atk = evoType.atk;
m.def = evoType.def;
m.spd = evoType.spd;
m.prod = evoType.prod;
m.sprite = generateMonsterSprite(evoType);
log(`${m.type.name} has EVOLVED!`);
showToast(`EVOLVED: ${evoType.name}!`);
spawnParticles(window.innerWidth/2, window.innerHeight/2, evoType.palette[0]);
closeModal();
render();
}
function sacrificeMonster(m) {
const soulGain = Math.floor(m.level * m.type.tier * 5 + 1);
state.souls += soulGain;
state.monsters = state.monsters.filter(x => x.id !== m.id);
log(`Sacrificed ${m.type.name} for ${soulGain} Souls.`);
showToast(`+${soulGain} Souls`);
closeModal();
render();
}
function buyUpgrade(key) {
const up = state.upgrades[key];
if (state.dread < up.cost) {
showToast("Not enough Dread!");
return;
}
state.dread -= up.cost;
up.level++;
up.cost = Math.floor(up.cost * up.costScale);
if (key === 'voidgate') {
state.zones.swamp.unlocked = true;
}
log(`Purchased: ${up.name} Lv.${up.level}`);
showToast(`${up.name} +1`);
render();
}
function enterZone(key) {
const zone = state.zones[key];
if (!zone.unlocked) {
showToast("Zone locked!");
return;
}
if (state.monsters.length === 0) {
showToast("You need monsters to farm!");
return;
}
state.activeZone = key;
state.zoneProgress = 0;
state.zoneHealth = 100;
log(`Entered ${zone.name}...`);
showToast(`Farming: ${zone.name}`);
render();
}
// ==================== PRODUCTION ====================
function gameTick() {
// Monster production
let mult = Math.pow(state.upgrades.amplifier.effect, state.upgrades.amplifier.level);
if (mult === Infinity || isNaN(mult)) mult = 1;
for (const m of state.monsters) {
const gain = m.prod * m.level * mult / 10; // per tick (100ms)
state.dread += gain;
state.totalDread += gain;
m.xp += 0.1 * m.level;
if (m.xp >= m.xpToLevel) {
m.xp = m.xpToLevel; // cap, manual level up
}
}
// Automation
if (state.upgrades.extractor.level > 0) {
state.dread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10;
state.totalDread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10;
}
if (state.upgrades.harvester.level > 0) {
state.souls += state.upgrades.harvester.level * state.upgrades.harvester.effect / 10;
}
// Zone farming
if (state.activeZone) {
const zone = state.zones[state.activeZone];
state.zoneProgress += 1;
// Danger check every 10 ticks
if (state.zoneProgress % 10 === 0) {
if (Math.random() < zone.danger) {
// Damage random monster
const target = state.monsters[Math.floor(Math.random() * state.monsters.length)];
const dmg = Math.floor(Math.random() * 10) + 5;
target.hp -= dmg;
if (target.hp <= 0) {
log(`${target.type.name} was consumed by the ${zone.name}...`);
state.monsters = state.monsters.filter(m => m.id !== target.id);
if (state.monsters.length === 0) {
state.activeZone = null;
log("All monsters lost. Retreating...");
}
}
}
}
// Rewards
state.dread += zone.dreadRate / 10;
state.totalDread += zone.dreadRate / 10;
state.souls += zone.soulRate / 10;
// Zone unlocks
if (state.activeZone === 'swamp' && state.zoneProgress > 300) {
state.zones.abyss.unlocked = true;
}
if (state.activeZone === 'abyss' && state.zoneProgress > 600) {
state.zones.nullspace.unlocked = true;
}
}
updateResources();
if (state.tab === 'monsters') renderMonsters();
if (state.tab === 'zones') renderZones();
}
function updateResources() {
document.getElementById('resDread').textContent = formatNum(state.dread);
document.getElementById('resSouls').textContent = formatNum(state.souls);
document.getElementById('resShards').textContent = formatNum(state.shards);
}
function formatNum(n) {
if (n >= 1e12) return (n/1e12).toFixed(2) + 'T';
if (n >= 1e9) return (n/1e9).toFixed(2) + 'B';
if (n >= 1e6) return (n/1e6).toFixed(2) + 'M';
if (n >= 1e3) return (n/1e3).toFixed(2) + 'K';
return Math.floor(n).toString();
}
// ==================== RENDERING ====================
function render() {
const content = document.getElementById('content');
content.innerHTML = '';
swit
保存された差分
原文
ファイルを開く
1
変更されたテキスト
ファイルを開く
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>ABYSSAL FARM — Void Breeder</title> <style> @import url('https://fonts.googleapis.com/css2?family=VT323&display=swap'); :root { --bg: #0a0a0f; --panel: #12121a; --panel2: #1a1a25; --accent: #8b0000; --accent2: #4a0080; --accent3: #1a5c1a; --text: #c0c0c0; --text-dim: #555; --border: #2a2a35; --glow-red: 0 0 8px #8b0000; --glow-purple: 0 0 8px #4a0080; --glow-green: 0 0 8px #1a5c1a; } * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: 'VT323', monospace; font-size: 18px; overflow: hidden; height: 100vh; width: 100vw; touch-action: manipulation; } /* CRT overlay */ body::before { content: ""; position: fixed; top:0; left:0; right:0; bottom:0; background: repeating-linear-gradient( 0deg, rgba(0,0,0,0.15), rgba(0,0,0,0.15) 1px, transparent 1px, transparent 2px ); pointer-events: none; z-index: 9999; } body::after { content: ""; position: fixed; top:0; left:0; right:0; bottom:0; background: radial-gradient(circle, transparent 60%, rgba(0,0,0,0.6) 100%); pointer-events: none; z-index: 9998; } #app { display: flex; flex-direction: column; height: 100vh; max-width: 600px; margin: 0 auto; position: relative; z-index: 1; } /* Header */ .header { background: var(--panel); border-bottom: 2px solid var(--border); padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .header h1 { margin: 0; font-size: 1.4rem; color: var(--accent); text-shadow: var(--glow-red); letter-spacing: 2px; } .resources { display: flex; gap: 12px; font-size: 0.95rem; } .res-item { display: flex; align-items: center; gap: 4px; } .res-icon { width: 14px; height: 14px; display: inline-block; } /* Tabs */ .tabs { display: flex; background: var(--panel); border-bottom: 2px solid var(--border); flex-shrink: 0; } .tab { flex: 1; padding: 10px; text-align: center; cursor: pointer; border: none; background: transparent; color: var(--text-dim); font-family: 'VT323', monospace; font-size: 1rem; border-bottom: 3px solid transparent; transition: all 0.2s; } .tab.active { color: var(--text); border-bottom-color: var(--accent); background: var(--panel2); } .tab:hover { color: var(--text); } /* Content area */ .content { flex: 1; overflow-y: auto; padding: 12px; position: relative; } .content::-webkit-scrollbar { width: 6px; } .content::-webkit-scrollbar-thumb { background: var(--accent); } /* Monster Cards */ .monster-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; } .monster-card { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 8px; text-align: center; cursor: pointer; transition: all 0.2s; position: relative; overflow: hidden; } .monster-card:hover { border-color: var(--accent); box-shadow: var(--glow-red); } .monster-card.selected { border-color: var(--accent2); box-shadow: var(--glow-purple); } .monster-canvas { width: 64px; height: 64px; image-rendering: pixelated; margin: 0 auto 6px; display: block; } .monster-name { font-size: 0.9rem; color: var(--accent); margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .monster-lvl { font-size: 0.8rem; color: var(--text-dim); } .monster-bar { width: 100%; height: 4px; background: #222; margin-top: 4px; border-radius: 2px; overflow: hidden; } .monster-bar-fill { height: 100%; background: var(--accent); transition: width 0.3s; } /* Hatch Button */ .hatch-area { text-align: center; padding: 20px; margin-bottom: 16px; } .hatch-btn { width: 120px; height: 120px; border-radius: 50%; border: 3px solid var(--accent); background: var(--panel); color: var(--accent); font-family: 'VT323', monospace; font-size: 1.2rem; cursor: pointer; position: relative; overflow: hidden; box-shadow: var(--glow-red); transition: transform 0.1s; display: inline-flex; align-items: center; justify-content: center; flex-direction: column; } .hatch-btn:active { transform: scale(0.92); } .hatch-btn .cost { font-size: 0.8rem; color: var(--text-dim); margin-top: 4px; } .hatch-btn::before { content: ""; position: absolute; width: 80%; height: 80%; border: 2px dashed var(--accent); border-radius: 50%; animation: spin 8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Upgrades / Automation */ .upgrade-list { display: flex; flex-direction: column; gap: 8px; } .upgrade-item { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 10px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: all 0.2s; } .upgrade-item:hover { border-color: var(--accent2); } .upgrade-item.disabled { opacity: 0.4; cursor: not-allowed; } .upgrade-info { flex: 1; } .upgrade-name { font-size: 1rem; color: var(--accent2); } .upgrade-desc { font-size: 0.8rem; color: var(--text-dim); } .upgrade-cost { background: var(--panel2); padding: 4px 10px; border-radius: 4px; border: 1px solid var(--border); font-size: 0.9rem; white-space: nowrap; } /* Farm Zones */ .zone-list { display: flex; flex-direction: column; gap: 10px; } .zone-card { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 12px; cursor: pointer; position: relative; } .zone-card:hover { border-color: var(--accent3); } .zone-name { font-size: 1.1rem; color: var(--accent3); margin-bottom: 4px; } .zone-desc { font-size: 0.85rem; color: var(--text-dim); } .zone-reward { font-size: 0.9rem; color: var(--text); margin-top: 6px; } .zone-danger { position: absolute; top: 8px; right: 8px; font-size: 0.75rem; color: var(--accent); } /* Detail Modal */ .modal-overlay { position: fixed; top:0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.85); display: none; align-items: center; justify-content: center; z-index: 10000; padding: 20px; } .modal-overlay.active { display: flex; } .modal { background: var(--panel); border: 2px solid var(--accent); border-radius: 8px; padding: 20px; max-width: 400px; width: 100%; max-height: 80vh; overflow-y: auto; position: relative; } .modal-close { position: absolute; top: 8px; right: 12px; background: none; border: none; color: var(--text); font-size: 1.5rem; cursor: pointer; font-family: 'VT323', monospace; } .modal-title { font-size: 1.3rem; color: var(--accent); margin-bottom: 12px; text-align: center; } .modal-canvas { width: 96px; height: 96px; image-rendering: pixelated; margin: 0 auto 12px; display: block; } .stat-row { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid var(--border); font-size: 0.95rem; } .action-btn { width: 100%; padding: 10px; margin-top: 10px; border: 2px solid var(--accent); background: var(--panel2); color: var(--text); font-family: 'VT323', monospace; font-size: 1rem; cursor: pointer; border-radius: 4px; transition: all 0.2s; } .action-btn:hover { background: var(--accent); color: #fff; } .action-btn:disabled { opacity: 0.3; cursor: not-allowed; } /* Floating text */ .float-text { position: absolute; color: var(--accent); font-size: 1rem; pointer-events: none; animation: floatUp 1s ease-out forwards; z-index: 100; } @keyframes floatUp { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-40px); } } /* Log */ .log-area { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 8px; height: 120px; overflow-y: auto; font-size: 0.85rem; color: var(--text-dim); } .log-entry { margin-bottom: 2px; } .log-entry.new { color: var(--text); } /* Glitch effect on title */ .glitch { animation: glitch 3s infinite; } @keyframes glitch { 0%, 90%, 100% { text-shadow: var(--glow-red); } 92% { text-shadow: 2px 0 #0ff, -2px 0 #f0f; } 94% { text-shadow: -2px 0 #0ff, 2px 0 #f0f; } 96% { text-shadow: var(--glow-red); } } /* Notification toast */ .toast { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background: var(--panel); border: 2px solid var(--accent); padding: 10px 20px; border-radius: 4px; font-size: 1rem; z-index: 10001; opacity: 0; transition: opacity 0.3s; pointer-events: none; white-space: nowrap; } .toast.show { opacity: 1; } /* Evolution tree */ .evo-tree { display: flex; justify-content: center; align-items: center; gap: 8px; margin: 12px 0; flex-wrap: wrap; } .evo-node { width: 48px; height: 48px; border: 2px solid var(--border); border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; color: var(--text-dim); background: var(--panel2); } .evo-node.active { border-color: var(--accent); color: var(--accent); box-shadow: var(--glow-red); } .evo-arrow { color: var(--text-dim); font-size: 1.2rem; } /* Particle canvas */ #particleCanvas { position: fixed; top: 0; left: 0; pointer-events: none; z-index: 9997; } /* Responsive */ @media (max-width: 400px) { .monster-grid { grid-template-columns: repeat(2, 1fr); } .header h1 { font-size: 1.1rem; } } </style> </head> <body> <canvas id="particleCanvas"></canvas> <div id="app"> <div class="header"> <h1 class="glitch">ABYSSAL FARM</h1> <div class="resources"> <div class="res-item"><span style="color:#8b0000">◆</span> <span id="resDread">0</span></div> <div class="res-item"><span style="color:#4a0080">◆</span> <span id="resSouls">0</span></div> <div class="res-item"><span style="color:#1a5c1a">◆</span> <span id="resShards">0</span></div> </div> </div> <div class="tabs"> <button class="tab active" data-tab="hatch">HATCH</button> <button class="tab" data-tab="monsters">MONSTERS</button> <button class="tab" data-tab="upgrades">VOID SHOP</button> <button class="tab" data-tab="zones">REALMS</button> </div> <div class="content" id="content"> <!-- Dynamic content --> </div> <div class="log-area" id="logArea"> <div class="log-entry">Welcome to the Abyss. Hatch your first abomination.</div> </div> </div> <!-- Modal --> <div class="modal-overlay" id="modalOverlay"> <div class="modal" id="modal"> <button class="modal-close" onclick="closeModal()">×</button> <div id="modalContent"></div> </div> </div> <div class="toast" id="toast"></div> <script> // ==================== GAME STATE ==================== const state = { dread: 0, souls: 0, shards: 0, totalDread: 0, monsters: [], nextId: 1, selectedMonster: null, tab: 'hatch', upgrades: { extractor: { level: 0, cost: 50, costScale: 1.6, name: "Void Extractor", desc: "Auto-generates +1 Dread/sec", effect: 1 }, harvester: { level: 0, cost: 200, costScale: 1.7, name: "Soul Harvester", desc: "Auto-generates +0.5 Souls/sec", effect: 0.5 }, incubator: { level: 0, cost: 500, costScale: 1.8, name: "Corrupt Incubator", desc: "Monsters hatch 1 tier higher base", effect: 1 }, amplifier: { level: 0, cost: 1000, costScale: 1.9, name: "Dread Amplifier", desc: "All monster production ×1.5", effect: 1.5 }, voidgate: { level: 0, cost: 5000, costScale: 2.0, name: "Void Gate", desc: "Unlocks Realm farming", effect: 1 }, mutagen: { level: 0, cost: 10000, costScale: 2.2, name: "Mutagen Vat", desc: "Evolution cost reduced by 10%", effect: 0.9 }, }, zones: { crypt: { unlocked: true, name: "Forgotten Crypt", desc: "Ancient burial grounds teeming with weak spirits", dreadRate: 5, soulRate: 0, danger: 0.05, reward: "+5 Dread/sec" }, swamp: { unlocked: false, name: "Rotting Swamp", desc: "Bubbling miasma corrupts everything it touches", dreadRate: 15, soulRate: 1, danger: 0.1, reward: "+15 Dread, +1 Soul/sec" }, abyss: { unlocked: false, name: "The Abyss", desc: "Stare into the void. It stares back.", dreadRate: 50, soulRate: 5, danger: 0.2, reward: "+50 Dread, +5 Souls/sec" }, nullspace: { unlocked: false, name: "Null Space", desc: "Reality unravels. Only the strongest survive.", dreadRate: 200, soulRate: 20, danger: 0.35, reward: "+200 Dread, +20 Souls/sec" }, }, activeZone: null, zoneProgress: 0, zoneHealth: 100, }; // ==================== MONSTER DATA ==================== const MONSTER_TYPES = [ // Tier 0 - Larvae { tier: 0, name: "Grubspawn", hp: 10, atk: 1, def: 0, spd: 1, prod: 0.5, evo: "Mawling", palette: [[0,80,0],[40,120,40],[20,60,20]] }, { tier: 0, name: "Boneling", hp: 8, atk: 2, def: 0, spd: 2, prod: 0.4, evo: "Ribcage", palette: [[200,200,200],[150,150,150],[100,100,100]] }, { tier: 0, name: "Sludge", hp: 15, atk: 1, def: 1, spd: 0, prod: 0.6, evo: "Muckfiend", palette: [[60,40,20],[100,80,40],[80,60,30]] }, // Tier 1 - Juveniles { tier: 1, name: "Mawling", hp: 30, atk: 4, def: 1, spd: 3, prod: 1.5, evo: "Goremaw", palette: [[120,0,0],[180,20,20],[80,0,0]] }, { tier: 1, name: "Ribcage", hp: 25, atk: 5, def: 2, spd: 4, prod: 1.2, evo: "Ossuary", palette: [[220,220,220],[180,180,180],[120,120,120]] }, { tier: 1, name: "Muckfiend", hp: 40, atk: 3, def: 3, spd: 1, prod: 1.8, evo: "Rotlord", palette: [[80,60,30],[120,100,50],[60,40,10]] }, { tier: 1, name: "Shadelet", hp: 20, atk: 6, def: 0, spd: 5, prod: 1.0, evo: "Dreadwraith", palette: [[40,0,60],[80,20,100],[20,0,40]] }, // Tier 2 - Adults { tier: 2, name: "Goremaw", hp: 80, atk: 10, def: 3, spd: 5, prod: 4.0, evo: "Fleshbeast", palette: [[160,0,0],[220,40,40],[100,0,0]] }, { tier: 2, name: "Ossuary", hp: 70, atk: 12, def: 5, spd: 6, prod: 3.5, evo: "Catacomb", palette: [[240,240,240],[200,200,200],[140,140,140]] }, { tier: 2, name: "Rotlord", hp: 100, atk: 8, def: 6, spd: 2, prod: 5.0, evo: "Plaguefather", palette: [[100,80,40],[140,120,60],[80,60,20]] }, { tier: 2, name: "Dreadwraith", hp: 60, atk: 15, def: 1, spd: 10, prod: 3.0, evo: "Voidreaper", palette: [[60,0,80],[100,30,120],[40,0,60]] }, // Tier 3 - Elders { tier: 3, name: "Fleshbeast", hp: 200, atk: 25, def: 8, spd: 6, prod: 12.0, evo: "Abomination", palette: [[180,20,20],[240,60,60],[120,0,0]] }, { tier: 3, name: "Catacomb", hp: 180, atk: 28, def: 12, spd: 8, prod: 10.0, evo: "Gravemind", palette: [[255,255,255],[220,220,220],[160,160,160]] }, { tier: 3, name: "Plaguefather", hp: 250, atk: 20, def: 15, spd: 3, prod: 15.0, evo: "Pestilence", palette: [[120,100,50],[160,140,70],[100,80,30]] }, { tier: 3, name: "Voidreaper", hp: 150, atk: 35, def: 4, spd: 15, prod: 9.0, evo: "Null Emperor", palette: [[80,0,100],[120,40,140],[60,0,80]] }, // Tier 4 - Apex { tier: 4, name: "Abomination", hp: 600, atk: 60, def: 20, spd: 8, prod: 40.0, evo: null, palette: [[200,0,0],[255,50,50],[150,0,0]] }, { tier: 4, name: "Gravemind", hp: 550, atk: 65, def: 25, spd: 10, prod: 35.0, evo: null, palette: [[255,255,255],[230,230,230],[180,180,180]] }, { tier: 4, name: "Pestilence", hp: 700, atk: 50, def: 30, spd: 4, prod: 50.0, evo: null, palette: [[140,120,60],[180,160,80],[120,100,40]] }, { tier: 4, name: "Null Emperor", hp: 500, atk: 80, def: 10, spd: 20, prod: 30.0, evo: null, palette: [[100,0,120],[140,50,160],[80,0,100]] }, ]; const HATCH_COST_BASE = 10; const HATCH_COST_SCALE = 1.15; let hatchCount = 0; // ==================== PIXEL ART GENERATOR ==================== function generateMonsterSprite(type, size = 64) { const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); const seed = type.name.split('').reduce((a,b)=>a+b.charCodeAt(0),0); const rng = mulberry32(seed + Date.now() % 10000); const palette = type.palette; const px = size / 16; // Background glow ctx.fillStyle = `rgba(${palette[0][0]},${palette[0][1]},${palette[0][2]},0.1)`; ctx.fillRect(0,0,size,size); // Body shape based on tier const bodyColor = `rgb(${palette[0][0]},${palette[0][1]},${palette[0][2]})`; const detailColor = `rgb(${palette[1][0]},${palette[1][1]},${palette[1][2]})`; const darkColor = `rgb(${palette[2][0]},${palette[2][1]},${palette[2][2]})`; ctx.fillStyle = bodyColor; // Procedural body const cx = 8, cy = 8; const bodyW = 6 + type.tier + Math.floor(rng()*4); const bodyH = 6 + type.tier + Math.floor(rng()*4); for(let y=0; y<16; y++) { for(let x=0; x<16; x++) { const dx = x - cx; const dy = y - cy; const dist = Math.sqrt(dx*dx + dy*dy); const noise = rng(); if (dist < bodyW/2 + noise) { // Core body ctx.fillStyle = bodyColor; ctx.fillRect(x*px, y*px, px, px); } else if (dist < bodyW/2 + 2 && noise > 0.4) { // Details / limbs ctx.fillStyle = detailColor; ctx.fillRect(x*px, y*px, px, px); } else if (dist < bodyW/2 + 3 && noise > 0.7) { // Dark accents ctx.fillStyle = darkColor; ctx.fillRect(x*px, y*px, px, px); } } } // Eyes const eyeY = cy - 1 + Math.floor(rng()*3); const eyeX1 = cx - 2 - Math.floor(rng()*2); const eyeX2 = cx + 2 + Math.floor(rng()*2); ctx.fillStyle = '#ff0000'; if (rng() > 0.3) ctx.fillRect(eyeX1*px, eyeY*px, px, px); if (rng() > 0.3) ctx.fillRect(eyeX2*px, eyeY*px, px, px); // Mouth if (type.tier >= 1) { ctx.fillStyle = darkColor; for(let i=-2; i<=2; i++) { if (rng() > 0.2) ctx.fillRect((cx+i)*px, (cy+3)*px, px, px); } } // Horns / spikes for higher tiers if (type.tier >= 2) { ctx.fillStyle = detailColor; ctx.fillRect((cx-3)*px, (cy-4)*px, px, px*2); ctx.fillRect((cx+3)*px, (cy-4)*px, px, px*2); } if (type.tier >= 3) { ctx.fillStyle = '#fff'; ctx.fillRect((cx-1)*px, (cy-5)*px, px, px*2); ctx.fillRect((cx+1)*px, (cy-5)*px, px, px*2); } return canvas; } function mulberry32(a) { return function() { let t = a += 0x6D2B79F5; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; } } // ==================== CORE FUNCTIONS ==================== function getHatchCost() { return Math.floor(HATCH_COST_BASE * Math.pow(HATCH_COST_SCALE, hatchCount)); } function hatchMonster() { const cost = getHatchCost(); if (state.dread < cost) { showToast("Not enough Dread!"); return; } state.dread -= cost; hatchCount++; const bonus = state.upgrades.incubator.level; const available = MONSTER_TYPES.filter(m => m.tier <= Math.min(bonus, 4)); const type = available[Math.floor(Math.random() * available.length)]; const monster = { id: state.nextId++, type: type, level: 1, xp: 0, xpToLevel: 10, hp: type.hp, maxHp: type.hp, atk: type.atk, def: type.def, spd: type.spd, prod: type.prod, sprite: null, }; monster.sprite = generateMonsterSprite(type); state.monsters.push(monster); log(`Hatched ${type.name}!`); showToast(`Hatched: ${type.name}`); spawnParticles(window.innerWidth/2, window.innerHeight/2, type.palette[0]); render(); } function levelUpMonster(m) { const cost = Math.floor(m.xpToLevel * 0.5); if (state.dread < cost) { showToast("Not enough Dread!"); return; } state.dread -= cost; m.level++; m.xp = 0; m.xpToLevel = Math.floor(m.xpToLevel * 1.5); m.maxHp = Math.floor(m.maxHp * 1.2); m.hp = m.maxHp; m.atk = Math.floor(m.atk * 1.15); m.def = Math.floor(m.def * 1.1); m.spd = Math.floor(m.spd * 1.1); m.prod = m.prod * 1.1; log(`${m.type.name} reached level ${m.level}!`); showToast(`${m.type.name} Lv.${m.level}`); render(); } function evolveMonster(m) { if (!m.type.evo) { showToast("Cannot evolve further!"); return; } const evoName = m.type.evo; const evoType = MONSTER_TYPES.find(t => t.name === evoName); if (!evoType) return; let cost = Math.floor(100 * Math.pow(2, evoType.tier) * Math.pow(state.upgrades.mutagen ? 0.9 : 1, state.upgrades.mutagen.level)); if (state.souls < cost) { showToast(`Need ${cost} Souls!`); return; } state.souls -= cost; m.type = evoType; m.level = 1; m.xp = 0; m.xpToLevel = 20; m.maxHp = evoType.hp; m.hp = evoType.hp; m.atk = evoType.atk; m.def = evoType.def; m.spd = evoType.spd; m.prod = evoType.prod; m.sprite = generateMonsterSprite(evoType); log(`${m.type.name} has EVOLVED!`); showToast(`EVOLVED: ${evoType.name}!`); spawnParticles(window.innerWidth/2, window.innerHeight/2, evoType.palette[0]); closeModal(); render(); } function sacrificeMonster(m) { const soulGain = Math.floor(m.level * m.type.tier * 5 + 1); state.souls += soulGain; state.monsters = state.monsters.filter(x => x.id !== m.id); log(`Sacrificed ${m.type.name} for ${soulGain} Souls.`); showToast(`+${soulGain} Souls`); closeModal(); render(); } function buyUpgrade(key) { const up = state.upgrades[key]; if (state.dread < up.cost) { showToast("Not enough Dread!"); return; } state.dread -= up.cost; up.level++; up.cost = Math.floor(up.cost * up.costScale); if (key === 'voidgate') { state.zones.swamp.unlocked = true; } log(`Purchased: ${up.name} Lv.${up.level}`); showToast(`${up.name} +1`); render(); } function enterZone(key) { const zone = state.zones[key]; if (!zone.unlocked) { showToast("Zone locked!"); return; } if (state.monsters.length === 0) { showToast("You need monsters to farm!"); return; } state.activeZone = key; state.zoneProgress = 0; state.zoneHealth = 100; log(`Entered ${zone.name}...`); showToast(`Farming: ${zone.name}`); render(); } // ==================== PRODUCTION ==================== function gameTick() { // Monster production let mult = Math.pow(state.upgrades.amplifier.effect, state.upgrades.amplifier.level); if (mult === Infinity || isNaN(mult)) mult = 1; for (const m of state.monsters) { const gain = m.prod * m.level * mult / 10; // per tick (100ms) state.dread += gain; state.totalDread += gain; m.xp += 0.1 * m.level; if (m.xp >= m.xpToLevel) { m.xp = m.xpToLevel; // cap, manual level up } } // Automation if (state.upgrades.extractor.level > 0) { state.dread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10; state.totalDread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10; } if (state.upgrades.harvester.level > 0) { state.souls += state.upgrades.harvester.level * state.upgrades.harvester.effect / 10; } // Zone farming if (state.activeZone) { const zone = state.zones[state.activeZone]; state.zoneProgress += 1; // Danger check every 10 ticks if (state.zoneProgress % 10 === 0) { if (Math.random() < zone.danger) { // Damage random monster const target = state.monsters[Math.floor(Math.random() * state.monsters.length)]; const dmg = Math.floor(Math.random() * 10) + 5; target.hp -= dmg; if (target.hp <= 0) { log(`${target.type.name} was consumed by the ${zone.name}...`); state.monsters = state.monsters.filter(m => m.id !== target.id); if (state.monsters.length === 0) { state.activeZone = null; log("All monsters lost. Retreating..."); } } } } // Rewards state.dread += zone.dreadRate / 10; state.totalDread += zone.dreadRate / 10; state.souls += zone.soulRate / 10; // Zone unlocks if (state.activeZone === 'swamp' && state.zoneProgress > 300) { state.zones.abyss.unlocked = true; } if (state.activeZone === 'abyss' && state.zoneProgress > 600) { state.zones.nullspace.unlocked = true; } } updateResources(); if (state.tab === 'monsters') renderMonsters(); if (state.tab === 'zones') renderZones(); } function updateResources() { document.getElementById('resDread').textContent = formatNum(state.dread); document.getElementById('resSouls').textContent = formatNum(state.souls); document.getElementById('resShards').textContent = formatNum(state.shards); } function formatNum(n) { if (n >= 1e12) return (n/1e12).toFixed(2) + 'T'; if (n >= 1e9) return (n/1e9).toFixed(2) + 'B'; if (n >= 1e6) return (n/1e6).toFixed(2) + 'M'; if (n >= 1e3) return (n/1e3).toFixed(2) + 'K'; return Math.floor(n).toString(); } // ==================== RENDERING ==================== function render() { const content = document.getElementById('content'); content.innerHTML = ''; switch(state.tab) { case 'hatch': renderHatch(content); break; case 'monsters': renderMonsters(content); break; case 'upgrades': renderUpgrades(content); break; case 'zones': renderZones(content); break; } } function renderHatch(container) { const cost = getHatchCost(); const div = document.createElement('div'); div.className = 'hatch-area'; div.innerHTML = ` <button class="hatch-btn" id="hatchBtn"> <div>HATCH</div> <div class="cost">${formatNum(cost)} Dread</div> </button> <div style="margin-top:16px;color:var(--text-dim);font-size:0.9rem;"> Monsters hatched: ${hatchCount}<br> Active: ${state.monsters.length} </div> <div style="margin-top:12px;"> <div style="font-size:0.85rem;color:var(--text-dim);margin-bottom:6px;">Quick Sacrifice All Weaklings</div> <button class="action-btn" id="sacAllBtn" style="border-color:var(--accent2);">Sacrifice Tier 0 Monsters</button> </div> `; container.appendChild(div); document.getElementById('hatchBtn').onclick = hatchMonster; document.getElementById('sacAllBtn').onclick = () => { const victims = state.monsters.filter(m => m.type.tier === 0); let souls = 0; victims.forEach(m => souls += Math.floor(m.level * 5 + 1)); state.souls += souls; state.monsters = state.monsters.filter(m => m.type.tier !== 0); log(`Sacrificed ${victims.length} weaklings for ${souls} Souls.`); showToast(`+${souls} Souls`); render(); }; } function renderMonsters(container) { if (!container) container = document.getElementById('content'); container.innerHTML = ''; if (state.monsters.length === 0) { container.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-dim);">No monsters. Hatch some abominations first.</div>'; return; } const grid = document.createElement('div'); grid.className = 'monster-grid'; for (const m of state.monsters) { const card = document.createElement('div'); card.className = 'monster-card' + (state.selectedMonster === m.id ? ' selected' : ''); const canvas = m.sprite || generateMonsterSprite(m.type); canvas.className = 'monster-canvas'; const xpPct = Math.min(100, (m.xp / m.xpToLevel) * 100); card.innerHTML = ''; card.appendChild(canvas); const info = document.createElement('div'); info.innerHTML = ` <div class="monster-name">${m.type.name}</div> <div class="monster-lvl">Lv.${m.level} | HP:${Math.floor(m.hp)}/${m.maxHp}</div> <div class="monster-bar"><div class="monster-bar-fill" style="width:${xpPct}%"></div></div> `; card.appendChild(info); card.onclick = (e) => { state.selectedMonster = m.id; openMonsterDetail(m, e.clientX, e.clientY); }; grid.appendChild(card); } container.appendChild(grid); } function renderUpgrades(container) { const list = document.createElement('div'); list.className = 'upgrade-list'; for (const [key, up] of Object.entries(state.upgrades)) { const item = document.createElement('div'); item.className = 'upgrade-item' + (state.dread < up.cost ? ' disabled' : ''); item.innerHTML = ` <div class="upgrade-info"> <div class="upgrade-name">${up.name} <span style="color:var(--text-dim)">Lv.${up.level}</span></div> <div class="upgrade-desc">${up.desc}</div> </div> <div class="upgrade-cost">${formatNum(up.cost)} D</div> `; item.onclick = () => buyUpgrade(key); list.appendChild(item); } container.appendChild(list); } function renderZones(container) { if (!container) container = document.getElementById('content'); container.innerHTML = ''; const list = document.createElement('div'); list.className = 'zone-list'; for (const [key, zone] of Object.entries(state.zones)) { const card = document.createElement('div'); card.className = 'zone-card'; if (state.activeZone === key) { card.style.borderColor = 'var(--accent3)'; card.style.boxShadow = 'var(--glow-green)'; } const progress = state.activeZone === key ? `<div class="monster-bar" style="margin-top:8px"><div class="monster-bar-fill" style="width:${Math.min(100, state.zoneProgress/10)}%;background:var(--accent3)"></div></div>` : ''; card.innerHTML = ` <div class="zone-danger">${zone.unlocked ? 'OPEN' : 'SEALED'}</div> <div class="zone-name">${zone.name}</div> <div class="zone-desc">${zone.desc}</div> <div class="zone-reward">${zone.reward}</div> ${progress} `; if (zone.unlocked) { card.onclick = () => enterZone(key); } else { card.style.opacity = '0.4'; } list.appendChild(card); } container.appendChild(list); } // ==================== MODAL ==================== function openMonsterDetail(m, x, y) { const overlay = document.getElementById('modalOverlay'); const content = document.getElementById('modalContent'); const evoTree = []; let curr = m.type; while(curr) { evoTree.push(curr); if (curr.evo) curr = MONSTER_TYPES.find(t => t.name === curr.evo); else break; } const evoHtml = evoTree.map((t, i) => ` <div class="evo-node ${t.name === m.type.name ? 'active' : ''}">${t.name}</div> ${i < evoTree.length-1 ? '<div class="evo-arrow">→</div>' : ''} `).join(''); const levelCost = Math.floor(m.xpToLevel * 0.5); const canLevel = state.dread >= levelCost && m.xp >= m.xpToLevel; const canEvo = m.type.evo && state.souls >= Math.floor(100 * Math.pow(2, MONSTER_TYPES.find(t=>t.name===m.type.evo)?.tier || 0)); const canvas = m.sprite || generateMonsterSprite(m.type); canvas.className = 'modal-canvas'; content.innerHTML = ''; content.appendChild(canvas); const info = document.createElement('div'); info.innerHTML = ` <div class="modal-title">${m.type.name} <span style="color:var(--text-dim)">Lv.${m.level}</span></div> <div class="evo-tree">${evoHtml}</div> <div class="stat-row"><span>HP</span><span>${Math.floor(m.hp)}/${m.maxHp}</span></div> <div class="stat-row"><span>ATK</span><span>${m.atk}</span></div> <div class="stat-row"><span>DEF</span><span>${m.def}</span></div> <div class="stat-row"><span>SPD</span><span>${m.spd}</span></div> <div class="stat-row"><span>Production</span><span>${m.prod.toFixed(2)}/s</span></div> <div class="stat-row"><span>XP</span><span>${Math.floor(m.xp)}/${m.xpToLevel}</span></div> <button class="action-btn" id="btnLevel" ${!canLevel ? 'disabled' : ''}> Level Up (${formatNum(levelCost)} Dread) </button> <button class="action-btn" id="btnEvo" style="border-color:var(--accent2);" ${!canEvo ? 'disabled' : ''}> EVOLVE (${formatNum(Math.floor(100 * Math.pow(2, MONSTER_TYPES.find(t=>t.name===m.type.evo)?.tier || 0)))} Souls) </button> <button class="action-btn" id="btnSac" style="border-color:var(--accent);"> SACRIFICE (+${Math.floor(m.level * m.type.tier * 5 + 1)} Souls) </button> `; content.appendChild(info); overlay.classList.add('active'); document.getElementById('btnLevel').onclick = () => levelUpMonster(m); document.getElementById('btnEvo').onclick = () => evolveMonster(m); document.getElementById('btnSac').onclick = () => sacrificeMonster(m); } function closeModal() { document.getElementById('modalOverlay').classList.remove('active'); state.selectedMonster = null; if (state.tab === 'monsters') renderMonsters(); } // ==================== UI HELPERS ==================== function log(msg) { const area = document.getElementById('logArea'); const entry = document.createElement('div'); entry.className = 'log-entry new'; entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; area.insertBefore(entry, area.firstChild); if (area.children.length > 50) area.removeChild(area.lastChild); setTimeout(() => entry.classList.remove('new'), 2000); } function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 1500); } function spawnParticles(x, y, color) { const canvas = document.getElementById('particleCanvas'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const particles = []; for(let i=0; i<20; i++) { particles.push({ x: x, y: y, vx: (Math.random()-0.5)*8, vy: (Math.random()-0.5)*8, life: 1, color: `rgb(${color[0]},${color[1]},${color[2]})` }); } function anim() { ctx.clearRect(0,0,canvas.width,canvas.height); let alive = false; for(const p of particles) { if(p.life <= 0) continue; alive = true; p.x += p.vx; p.y += p.vy; p.life -= 0.03; ctx.globalAlpha = p.life; ctx.fillStyle = p.color; ctx.fillRect(p.x, p.y, 4, 4); } ctx.globalAlpha = 1; if(alive) requestAnimationFrame(anim); else ctx.clearRect(0,0,canvas.width,canvas.height); } anim(); } // ==================== TABS ==================== document.querySelectorAll('.tab').forEach(tab => { tab.onclick = () => { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); state.tab = tab.dataset.tab; render(); }; }); // ==================== INIT ==================== render(); setInterval(gameTick, 100); // Initial free monster setTimeout(() => { state.dread = 15; hatchMonster(); }, 100); </script> </body> </html>
違いを見つける