CHAT
TREE
can you make a game like saterd pixle dongon
BUDDY
Awesome idea, Tree! I'm making you a Stardew Valley / Pixel Dungeon style game — you'll explore a little dungeon, collect treasure, and fight monsters! Let me build it for you! 🎮✨
TREE
but with hollow night grafics
BUDDY
Ooh, Hollow Knight style! Dark, spooky, and super cool! I made the game look more like Hollow Knight — dark caves, a little knight hero, creepy bugs and monsters, and that awesome gloomy vibe! ⚔️🦋
TREE
and stats
BUDDY
Great idea! I added a full stats panel — you can see your Attack, Defense, and level progress, plus a proper health bar and geo counter! Level up as you go deeper! ⚔️✨
CODE
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hollow Dungeon</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { background: #0a0a0f; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; font-family: 'Georgia', serif; color: #ccc; } h1 { font-size: 1.7em; color: #b0c4ff; text-shadow: 0 0 16px #5577ff, 0 0 4px #fff; margin-bottom: 8px; letter-spacing: 4px; } #main-layout { display: flex; flex-direction: row; gap: 14px; align-items: flex-start; } #left-panel { display: flex; flex-direction: column; align-items: center; gap: 8px; } #stats-panel { background: #0a0a1a; border: 2px solid #3344aa; border-radius: 10px; padding: 12px 14px; min-width: 150px; box-shadow: 0 0 12px #2233aa44; font-size: 0.88em; color: #aabbff; display: flex; flex-direction: column; gap: 8px; } #stats-panel h2 { color: #b0c4ff; font-size: 1em; text-align: center; letter-spacing: 2px; text-shadow: 0 0 8px #5577ff; margin-bottom: 2px; } .stat-row { display: flex; flex-direction: column; gap: 2px; } .stat-label { color: #7788cc; font-size: 0.82em; letter-spacing: 1px; } .stat-bar-bg { background: #0d0d22; border-radius: 4px; height: 10px; width: 100%; border: 1px solid #2233aa; overflow: hidden; } .stat-bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; } .stat-value { font-size: 0.9em; color: #ccd8ff; font-weight: bold; } .divider { border: none; border-top: 1px solid #2233aa55; margin: 2px 0; } .stat-simple { display: flex; justify-content: space-between; align-items: center; } .stat-simple .stat-label { font-size: 0.85em; } .stat-simple .stat-value { color: #eeddff; } #hud { display: flex; gap: 10px; font-size: 0.88em; background: #0d0d18; padding: 5px 12px; border-radius: 10px; border: 2px solid #3344aa; box-shadow: 0 0 10px #2233aa55; flex-wrap: wrap; justify-content: center; } #hud span { color: #aabbff; font-weight: bold; } #canvas { border: 3px solid #2233aa; border-radius: 4px; image-rendering: pixelated; background: #080810; box-shadow: 0 0 30px #1122aa55, 0 0 8px #000; } #msg { margin-top: 6px; font-size: 0.88em; color: #99aaff; min-height: 1.4em; text-align: center; text-shadow: 0 0 8px #4455cc; max-width: 320px; } #controls { margin-top: 8px; display: grid; grid-template-columns: repeat(3, 46px); grid-template-rows: repeat(2, 46px); gap: 4px; } .btn { background: #0d0d22; border: 2px solid #3344aa; color: #aabbff; font-size: 1.2em; border-radius: 8px; cursor: pointer; user-select: none; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 8px #2233aa44; transition: background 0.1s; } .btn:active { background: #2233aa; color: #fff; } #btn-up { grid-column: 2; grid-row: 1; } #btn-left { grid-column: 1; grid-row: 2; } #btn-down { grid-column: 2; grid-row: 2; } #btn-right { grid-column: 3; grid-row: 2; } #restart-btn { margin-top: 8px; background: #0d0d22; border: 2px solid #3344aa; color: #aabbff; font-family: Georgia, serif; font-size: 1em; padding: 7px 22px; border-radius: 8px; cursor: pointer; display: none; box-shadow: 0 0 8px #2233aa55; } #restart-btn:hover { background: #2233aa; color: #fff; } /* XP bar at bottom of stats */ #xp-bar-fill { background: linear-gradient(90deg, #5566ff, #aabbff); } #hp-bar-fill { background: linear-gradient(90deg, #2244cc, #66aaff); } #atk-bar-fill { background: linear-gradient(90deg, #882200, #ff6644); } #def-bar-fill { background: linear-gradient(90deg, #226622, #66ff88); } </style> </head> <body> <h1>⚔ HOLLOW DUNGEON ⚔</h1> <div id="main-layout"> <!-- LEFT: stats --> <div id="stats-panel"> <h2>📜 STATS</h2> <!-- HP --> <div class="stat-row"> <div class="stat-label">💙 SOUL (HP)</div> <div class="stat-bar-bg"><div class="stat-bar-fill" id="hp-bar-fill" style="width:100%"></div></div> <div class="stat-value"><span id="hp-val">10</span> / <span id="hp-max-val">10</span></div> </div> <hr class="divider"> <!-- XP --> <div class="stat-row"> <div class="stat-label">⭐ XP TO NEXT</div> <div class="stat-bar-bg"><div class="stat-bar-fill" id="xp-bar-fill" style="width:0%"></div></div> <div class="stat-value"><span id="xp-val">0</span> / <span id="xp-max-val">5</span></div> </div> <hr class="divider"> <!-- ATK --> <div class="stat-row"> <div class="stat-label">⚔ NAIL (ATK)</div> <div class="stat-bar-bg"><div class="stat-bar-fill" id="atk-bar-fill" style="width:50%"></div></div> <div class="stat-value" id="atk-val">2</div> </div> <!-- DEF --> <div class="stat-row"> <div class="stat-label">🛡 SHELL (DEF)</div> <div class="stat-bar-bg"><div class="stat-bar-fill" id="def-bar-fill" style="width:0%"></div></div> <div class="stat-value" id="def-val">0</div> </div> <hr class="divider"> <!-- Simple stats --> <div class="stat-simple"> <span class="stat-label">🗺 DEPTH</span> <span class="stat-value" id="lvl-val2">1</span> </div> <div class="stat-simple"> <span class="stat-label">✨ GEO</span> <span class="stat-value" id="gold-val2">0</span> </div> <div class="stat-simple"> <span class="stat-label">👑 LVL</span> <span class="stat-value" id="plr-lvl-val">1</span> </div> <div class="stat-simple"> <span class="stat-label">💀 KILLS</span> <span class="stat-value" id="kills-val">0</span> </div> <div class="stat-simple"> <span class="stat-label">🔮 CHARMS</span> <span class="stat-value" id="charms-val">0</span> </div> </div> <!-- RIGHT: game + controls --> <div id="left-panel"> <div id="hud"> 💙 <span id="hp-hud">10</span>/10 | ✨ <span id="gold-val">0</span> | 🗺 Depth <span id="lvl-val">1</span> | 👑 Lv<span id="plr-lvl-hud">1</span> </div> <canvas id="canvas" width="320" height="320"></canvas> <div id="msg">Move with arrow keys or buttons. Explore the darkness!</div> <div id="controls"> <button class="btn" id="btn-up">▲</button> <button class="btn" id="btn-left">◀</button> <button class="btn" id="btn-down">▼</button> <button class="btn" id="btn-right">▶</button> </div> <button id="restart-btn" onclick="restartGame()">🔄 Try Again</button> </div> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const TILE = 32; const COLS = 10, ROWS = 10; let map, player, monsters, items, level, gameOver; function randInt(a, b) { return Math.floor(Math.random() * (b - a + 1)) + a; } function shuffle(arr) { for (let i = arr.length - 1; i > 0; i--) { let j = randInt(0, i); [arr[i], arr[j]] = [arr[j], arr[i]]; } } function restartGame() { level = 1; document.getElementById('restart-btn').style.display = 'none'; initGame(); } function initGame() { gameOver = false; document.getElementById('restart-btn').style.display = 'none'; document.getElementById('msg').textContent = 'Move with arrow keys or buttons. Explore the darkness!'; generateMap(); render(); } function generateMap() { map = []; for (let r = 0; r < ROWS; r++) { map[r] = []; for (let c = 0; c < COLS; c++) map[r][c] = { type: 'wall', visible: false, seen: false }; } let rooms = []; let attempts = 0; while (rooms.length < 6 && attempts < 300) { attempts++; let rw = randInt(2, 4), rh = randInt(2, 4); let rx = randInt(1, COLS - rw - 1), ry = randInt(1, ROWS - rh - 1); let overlap = false; for (let rm of rooms) { if (rx < rm.x+rm.w+1 && rx+rw+1 > rm.x && ry < rm.y+rm.h+1 && ry+rh+1 > rm.y) { overlap = true; break; } } if (!overlap) { rooms.push({ x: rx, y: ry, w: rw, h: rh }); for (let r = ry; r < ry+rh; r++) for (let c = rx; c < rx+rw; c++) map[r][c].type = 'floor'; } } for (let i = 1; i < rooms.length; i++) { let a = rooms[i-1], b = rooms[i]; let ax = randInt(a.x, a.x+a.w-1), ay = randInt(a.y, a.y+a.h-1); let bx = randInt(b.x, b.x+b.w-1), by = randInt(b.y, b.y+b.h-1); let cx = ax, cy = ay; while (cx !== bx) { map[cy][cx].type = 'floor'; cx += cx<bx?1:-1; } while (cy !== by) { map[cy][cx].type = 'floor'; cy += cy<by?1:-1; } map[cy][cx].type = 'floor'; } let r0 = rooms[0]; // Carry over stats between levels or start fresh let isNew = (level === 1 && (!player || player.gold === undefined)); let prevHp = player ? player.hp : 10; let prevGold = player ? player.gold : 0; let prevXP = player ? player.xp : 0; let prevPLvl = player ? player.plvl : 1; let prevAtk = player ? player.attack : 2; let prevDef = player ? player.defense : 0; let prevKills= player ? player.kills : 0; let prevCharms=player ? player.charms : 0; let maxHp = 10 + (prevPLvl - 1) * 3; player = { x: r0.x + Math.floor(r0.w/2), y: r0.y + Math.floor(r0.h/2), hp: isNew ? 10 : Math.min(prevHp + 3, maxHp), maxHp: maxHp, gold: isNew ? 0 : prevGold, attack: isNew ? 2 : prevAtk, defense: isNew ? 0 : prevDef, xp: isNew ? 0 : prevXP, xpNext: xpNeeded(isNew ? 1 : prevPLvl), plvl: isNew ? 1 : prevPLvl, kills: isNew ? 0 : prevKills, charms: isNew ? 0 : prevCharms, facing: 1, }; let rLast = rooms[rooms.length-1]; let ex = rLast.x + Math.floor(rLast.w/2), ey = rLast.y + Math.floor(rLast.h/2); map[ey][ex].type = 'exit'; monsters = []; for (let i = 1; i < rooms.length - 1; i++) { let rm = rooms[i]; let mx = rm.x + randInt(0, rm.w-1), my = rm.y + randInt(0, rm.h-1); if (!(mx===player.x && my===player.y)) { let types = [ { name:'Crawlid', emoji:'🪲', col:'#44ff88' }, { name:'Vengefly', emoji:'🦟', col:'#ff8844' }, { name:'Baldur', emoji:'🐛', col:'#ffcc44' }, { name:'Gruzzer', emoji:'🪰', col:'#cc44ff' }, { name:'Aspid', emoji:'🦂', col:'#ff4466' }, ]; let t = types[randInt(0, types.length-1)]; monsters.push({ x:mx, y:my, hp:3+level, maxHp:3+level, attack:1+Math.floor(level/2), defense:0, ...t }); } } items = []; let floorTiles = []; for (let r=0;r<ROWS;r++) for (let c=0;c<COLS;c++) if (map[r][c].type==='floor') floorTiles.push({x:c,y:r}); shuffle(floorTiles); let placed = 0; for (let ft of floorTiles) { if (placed >= 7) break; if (ft.x===player.x && ft.y===player.y) continue; if (monsters.some(m=>m.x===ft.x&&m.y===ft.y)) continue; let t = Math.random(); if (t<0.5) items.push({x:ft.x,y:ft.y,type:'geo',value:randInt(1,6)}); else if (t<0.75) items.push({x:ft.x,y:ft.y,type:'soul',value:3}); else items.push({x:ft.x,y:ft.y,type:'charm',value:1}); placed++; } updateHUD(); updateFOV(); } function xpNeeded(plvl) { return 5 + (plvl - 1) * 3; } function gainXP(amount) { player.xp += amount; while (player.xp >= player.xpNext) { player.xp -= player.xpNext; player.plvl++; player.xpNext = xpNeeded(player.plvl); player.attack++; player.defense = Math.floor(player.plvl / 3); player.maxHp += 3; player.hp = Math.min(player.hp + 3, player.maxHp); document.getElementById('msg').textContent = `✨ LEVEL UP! Lv${player.plvl} — Nail+1, Shell+${player.defense}, Soul+3!`; } } function updateFOV() { for (let r=0;r<ROWS;r++) for (let c=0;c<COLS;c++) map[r][c].visible = false; let radius = 3; for (let dy=-radius;dy<=radius;dy++) { for (let dx=-radius;dx<=radius;dx++) { if (dx*dx+dy*dy>radius*radius) continue; let cx=player.x+dx, cy=player.y+dy; if (cx<0||cx>=COLS||cy<0||cy>=ROWS) continue; if (lineOfSight(player.x,player.y,cx,cy)) { map[cy][cx].visible=true; map[cy][cx].seen=true; } } } } function lineOfSight(x0,y0,x1,y1) { let dx=Math.abs(x1-x0),dy=Math.abs(y1-y0); let sx=x0<x1?1:-1,sy=y0<y1?1:-1; let err=dx-dy,cx=x0,cy=y0; while(true) { if(cx===x1&&cy===y1) return true; if(cx!==x0||cy!==y0) if(map[cy][cx].type==='wall') return false; let e2=2*err; if(e2>-dy){err-=dy;cx+=sx;} if(e2<dx){err+=dx;cy+=sy;} } } function tryMove(dx,dy) { if(gameOver) return; let nx=player.x+dx, ny=player.y+dy; if(nx<0||nx>=COLS||ny<0||ny>=ROWS) return; let tile=map[ny][nx]; if(tile.type==='wall') return; if(dx!==0) player.facing=dx; let mi=monsters.findIndex(m=>m.x===nx&&m.y===ny); if(mi!==-1) { let m=monsters[mi]; let dmg=Math.max(1,player.attack-(m.defense||0)); m.hp-=dmg; let msg=`You strike the ${m.name} for ${dmg}!`; if(m.hp<=0) { let xpGain = 1 + Math.floor(level/2); monsters.splice(mi,1); player.kills++; gainXP(xpGain); msg=`${m.emoji} ${m.name} falls! +${xpGain}XP ✨`; } else { let mdmg=Math.max(1,m.attack-player.defense); player.hp-=mdmg; msg+=` It hits back for ${mdmg}!`; if(player.hp<=0){player.hp=0;gameOver=true;} } document.getElementById('msg').textContent=msg; updateHUD(); if(gameOver) endGame(); render(); return; } player.x=nx; player.y=ny; let ii=items.findIndex(it=>it.x===nx&&it.y===ny); if(ii!==-1) { let it=items[ii]; items.splice(ii,1); if(it.type==='geo') { player.gold+=it.value; document.getElementById('msg').textContent=`✨ Collected ${it.value} Geo!`; } else if(it.type==='soul') { player.hp=Math.min(player.maxHp,player.hp+it.value); document.getElementById('msg').textContent=`💙 Soul restored! +${it.value}`; } else { player.attack++; player.charms++; document.getElementById('msg').textContent=`🔮 Charm equipped! Attack +1!`; } } if(tile.type==='exit') { level++; document.getElementById('msg').textContent=`🌑 Descending deeper... Depth ${level}`; updateHUD(); render(); setTimeout(()=>{ generateMap(); render(); },600); return; } // Monster AI for(let m of monsters) { if(!map[m.y][m.x].visible) continue; let mdx=0,mdy=0; if(Math.abs(m.x-player.x)>Math.abs(m.y-player.y)) mdx=m.x<player.x?1:-1; else mdy=m.y<player.y?1:-1; let tx=m.x+mdx,ty=m.y+mdy; if(tx===player.x&&ty===player.y) { let mdmg=Math.max(1,m.attack-player.defense); player.hp-=mdmg; document.getElementById('msg').textContent=`${m.emoji} ${m.name} sneaks up and hits for ${mdmg}!`; if(player.hp<=0){player.hp=0;gameOver=true;endGame();} } else if(tx>=0&&tx<COLS&&ty>=0&&ty<ROWS&&map[ty][tx].type!=='wall'&&!monsters.some(o=>o.x===tx&&o.y===ty)) { m.x=tx;m.y=ty; } } updateHUD(); updateFOV(); render(); } function endGame() { document.getElementById('msg').textContent=`💀 You perished in the darkness... Depth ${level} | Kills: ${player.kills} | Geo: ${player.gold}`; document.getElementById('restart-btn').style.display='inline-block'; level=1; render(); } function updateHUD() { // HUD bar document.getElementById('hp-hud').textContent = player.hp; document.getElementById('gold-val').textContent = player.gold; document.getElementById('lvl-val').textContent = level; document.getElementById('plr-lvl-hud').textContent = player.plvl; // Stats panel document.getElementById('hp-val').textContent = player.hp; document.getElementById('hp-max-val').textContent = player.maxHp; document.getElementById('hp-bar-fill').style.width = Math.max(0,(player.hp/player.maxHp)*100)+'%'; document.getElementById('xp-val').textContent = player.xp; document.getElementById('xp-max-val').textContent = player.xpNext; document.getElementById('xp-bar-fill').style.width = Math.max(0,(player.xp/player.xpNext)*100)+'%'; let maxAtk = 10; document.getElementById('atk-val').textContent = player.attack; document.getElementById('atk-bar-fill').style.width = Math.min(100,(player.attack/maxAtk)*100)+'%'; let maxDef = 8; document.getElementById('def-val').textContent = player.defense; document.getElementById('def-bar-fill').style.width = Math.min(100,(player.defense/maxDef)*100)+'%'; document.getElementById('lvl-val2').textContent = level; document.getElementById('gold-val2').textContent = player.gold; document.getElementById('plr-lvl-val').textContent = player.plvl; document.getElementById('kills-val').textContent = player.kills; document.getElementById('charms-val').textContent = player.charms; } // ---- DRAWING ---- function drawKnight(x, y, facing) { let px = x * TILE, py = y * TILE; let f = facing >= 0 ? 1 : -1; ctx.save(); ctx.translate(px + TILE/2, py + TILE/2); ctx.scale(f, 1); ctx.fillStyle = '#111122'; ctx.beginPath(); ctx.ellipse(0, 10, 10, 7, 0, 0, Math.PI*2); ctx.fill(); ctx.fillStyle = '#1a1a2e'; ctx.fillRect(-6, -4, 12, 14); ctx.fillStyle = '#0f0f1e'; ctx.beginPath(); ctx.arc(0, -6, 8, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = '#b0c4ff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(-3, -13); ctx.lineTo(0, -20); ctx.lineTo(3, -13); ctx.stroke(); ctx.fillStyle = '#ddeeff'; ctx.shadowColor = '#aaccff'; ctx.shadowBlur = 6; ctx.beginPath(); ctx.ellipse(3, -7, 2.5, 3, 0, 0, Math.PI*2); ctx.fill(); ctx.shadowBlur = 0; ctx.strokeStyle = '#c8d8ff'; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.moveTo(6, 4); ctx.lineTo(16, -4); ctx.stroke(); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(15,-5);ctx.lineTo(17,-3); ctx.stroke(); ctx.fillStyle = '#0f0f1e'; ctx.fillRect(-6, 9, 4, 6); ctx.fillRect(2, 9, 4, 6); ctx.restore(); } function drawMonster(m) { if(!map[m.y][m.x].visible) return; let px=m.x*TILE, py=m.y*TILE; ctx.save(); ctx.translate(px+TILE/2, py+TILE/2); ctx.fillStyle='#1a0a2e'; ctx.beginPath(); ctx.ellipse(0,2,9,7,0,0,Math.PI*2); ctx.fill(); ctx.shadowColor=m.col; ctx.shadowBlur=8; ctx.fillStyle=m.col; ctx.beginPath(); ctx.ellipse(0,0,7,5,0,0,Math.PI*2); ctx.fill(); ctx.shadowBlur=0; ctx.fillStyle='#fff'; ctx.beginPath();ctx.arc(-3,-1,2,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(3,-1,2,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#000'; ctx.beginPath();ctx.arc(-3,-1,1,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(3,-1,1,0,Math.PI*2);ctx.fill(); ctx.strokeStyle=m.col;ctx.lineWidth=1.5; for(let i=-1;i<=1;i+=2) { ctx.beginPath();ctx.moveTo(i*6,3);ctx.lineTo(i*11,8);ctx.stroke(); ctx.beginPath();ctx.moveTo(i*5,0);ctx.lineTo(i*12,2);ctx.stroke(); } ctx.restore(); let bw=TILE-6; ctx.fillStyle='#200022'; ctx.fillRect(px+3, py+TILE-5, bw, 4); ctx.fillStyle=m.col; ctx.fillRect(px+3, py+TILE-5, Math.max(0,bw*m.hp/m.maxHp), 4); } function drawWall(x, y, dark) { let px=x*TILE, py=y*TILE; ctx.fillStyle=dark?'#050510':'#0d0d1e'; ctx.fillRect(px,py,TILE,TILE); if(!dark) { ctx.fillStyle='#111128'; ctx.fillRect(px+2,py+2,TILE-4,14); ctx.fillRect(px+2,py+18,TILE-4,12); ctx.strokeStyle='#1a1a3a'; ctx.lineWidth=1; ctx.strokeRect(px+2,py+2,TILE-4,14); ctx.strokeRect(px+2,py+18,TILE-4,12); ctx.fillStyle='#0a0a2211'; ctx.fillRect(px,py,TILE,TILE); } } function drawFloor(x, y, dark) { let px=x*TILE, py=y*TILE; ctx.fillStyle=dark?'#06060f':'#0c0c1a'; ctx.fillRect(px,py,TILE,TILE); if(!dark) { ctx.strokeStyle='#151530'; ctx.lineWidth=1; ctx.beginPath(); ctx.moveTo(px+4,py+TILE-4);ctx.lineTo(px+10,py+TILE-10); ctx.stroke(); ctx.beginPath(); ctx.moveTo(px+TILE-8,py+6);ctx.lineTo(px+TILE-4,py+12); ctx.stroke(); ctx.fillStyle='#1020ff06'; ctx.fillRect(px,py,TILE,TILE); } } function drawExit(x, y, dark) { let px=x*TILE, py=y*TILE; ctx.fillStyle=dark?'#040410':'#080820'; ctx.fillRect(px,py,TILE,TILE); if(!dark) { ctx.shadowColor='#4466ff'; ctx.shadowBlur=16; ctx.fillStyle='#0a0a33'; ctx.beginPath(); ctx.ellipse(px+TILE/2, py+TILE/2, 10, 13, 0, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle='#5577ff'; ctx.lineWidth=2; ctx.stroke(); ctx.shadowBlur=0; ctx.fillStyle='#2233aa44'; ctx.beginPath(); ctx.ellipse(px+TILE/2,py+TILE/2,6,9,0,0,Math.PI*2); ctx.fill(); ctx.fillStyle='#7799ff'; ctx.font='7px monospace'; ctx.fillText('⬡',px+11,py+10); ctx.fillText('⬡',px+18,py+26); } } function drawItem(it) { if(!map[it.y][it.x].visible) return; let px=it.x*TILE, py=it.y*TILE; ctx.save(); ctx.translate(px+TILE/2, py+TILE/2); if(it.type==='geo') { ctx.shadowColor='#aaffee';ctx.shadowBlur=8; ctx.fillStyle='#55ffcc'; ctx.beginPath(); ctx.moveTo(0,-8);ctx.lineTo(6,5);ctx.lineTo(-6,5);ctx.closePath();ctx.fill(); ctx.fillStyle='#aaffee'; ctx.beginPath(); ctx.moveTo(0,-8);ctx.lineTo(0,5);ctx.lineTo(-6,5);ctx.closePath();ctx.fill(); ctx.shadowBlur=0; } else if(it.type==='soul') { ctx.shadowColor='#99aaff';ctx.shadowBlur=10; ctx.fillStyle='#3344aa'; ctx.beginPath();ctx.arc(0,0,7,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#aabbff'; ctx.beginPath();ctx.arc(0,0,4,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#ffffff'; ctx.beginPath();ctx.arc(-2,-2,2,0,Math.PI*2);ctx.fill(); ctx.shadowBlur=0; } else { ctx.shadowColor='#ffaaff';ctx.shadowBlur=8; ctx.fillStyle='#884499'; ctx.beginPath();ctx.arc(0,0,7,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#ffccff'; ctx.font='10px serif'; ctx.textAlign='center';ctx.textBaseline='middle'; ctx.fillText('✦',0,1); ctx.shadowBlur=0; } ctx.restore(); } function render() { ctx.clearRect(0,0,canvas.width,canvas.height); let grad=ctx.createRadialGradient( player.x*TILE+TILE/2, player.y*TILE+TILE/2, 10, player.x*TILE+TILE/2, player.y*TILE+TILE/2, 180 ); grad.addColorStop(0,'#0a0a1888'); grad.addColorStop(1,'#00000000'); for(let r=0;r<ROWS;r++) { for(let c=0;c<COLS;c++) { let tile=map[r][c]; let x=c,y=r; if(!tile.seen) { ctx.fillStyle='#000'; ctx.fillRect(x*TILE,y*TILE,TILE,TILE); continue; } let dark=!tile.visible; if(tile.type==='wall') drawWall(x,y,dark); else if(tile.type==='exit') drawExit(x,y,dark); else drawFloor(x,y,dark); } } ctx.fillStyle=grad; ctx.fillRect(0,0,canvas.width,canvas.height); for(let it of items) drawItem(it); for(let m of monsters) drawMonster(m); drawKnight(player.x, player.y, player.facing); let bx=player.x*TILE+2, by=player.y*TILE+TILE-5, bw=TILE-4, bh=4; ctx.fillStyle='#050520'; ctx.fillRect(bx,by,bw,bh); ctx.fillStyle='#4466ff'; ctx.shadowColor='#4466ff';ctx.shadowBlur=4; ctx.fillRect(bx,by,Math.max(0,bw*player.hp/player.maxHp),bh); ctx.shadowBlur=0; } document.addEventListener('keydown',e=>{ if(e.key==='ArrowUp'||e.key==='w'){e.preventDefault();tryMove(0,-1);} else if(e.key==='ArrowDown'||e.key==='s'){e.preventDefault();tryMove(0,1);} else if(e.key==='ArrowLeft'||e.key==='a'){e.preventDefault();tryMove(-1,0);} else if(e.key==='ArrowRight'||e.key==='d'){e.preventDefault();tryMove(1,0);} }); document.getElementById('btn-up').addEventListener('click',()=>tryMove(0,-1)); document.getElementById('btn-down').addEventListener('click',()=>tryMove(0,1)); document.getElementById('btn-left').addEventListener('click',()=>tryMove(-1,0)); document.getElementById('btn-right').addEventListener('click',()=>tryMove(1,0)); level=1; player=null; initGame(); </script> </body> </html>
PREVIEW
PICTURES0/50
No pictures yet. Add one, then ask your buddy to put it in your app!