{"id":39,"date":"2026-08-26T22:14:23","date_gmt":"2026-08-26T22:14:23","guid":{"rendered":"https:\/\/ethannukoop.com\/?page_id=39"},"modified":"2026-08-26T23:09:58","modified_gmt":"2026-08-26T23:09:58","slug":"dragons-lair","status":"publish","type":"page","link":"https:\/\/ethannukoop.com\/?page_id=39","title":{"rendered":"Dragon&#8217;s Lair"},"content":{"rendered":"\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>Dragon&#8217;s Lair Ultimate<\/title>\n\n<style>\n*{box-sizing:border-box}\n\nbody{\n    margin:0;\n    background:#050609;\n    color:white;\n    font-family:Arial,sans-serif;\n    display:flex;\n    justify-content:center;\n}\n\n#game{\n    width:min(900px,100%);\n    background:#090b10;\n}\n\nh1{\n    margin:0;\n    padding:12px;\n    text-align:center;\n    color:#ffd45a;\n    background:#210d08;\n}\n\n#stats{\n    display:flex;\n    justify-content:center;\n    flex-wrap:wrap;\n    gap:10px;\n    padding:9px;\n    background:#151922;\n    font-size:13px;\n    font-weight:bold;\n}\n\ncanvas{\n    display:block;\n    width:100%;\n    height:auto;\n    background:#080a0e;\n    touch-action:none;\n}\n\n#boss{\n    display:none;\n    background:#250000;\n    padding:8px;\n}\n\n#bossName{\n    text-align:center;\n    color:#ff5b3b;\n    font-weight:bold;\n}\n\n#bossBar{\n    height:18px;\n    background:#500000;\n}\n\n#bossHP{\n    width:100%;\n    height:100%;\n    background:linear-gradient(90deg,#f00,#ff9d00);\n}\n\n#log{\n    height:95px;\n    overflow-y:auto;\n    padding:8px;\n    background:#030303;\n    font:12px monospace;\n}\n\n.controls{\n    display:grid;\n    grid-template-columns:65px 65px 65px;\n    grid-template-rows:65px 65px;\n    justify-content:center;\n    gap:5px;\n    padding:10px;\n    background:#160d09;\n}\n\nbutton{\n    border:2px solid #92552a;\n    border-radius:9px;\n    color:white;\n    background:linear-gradient(#482516,#190c08);\n    font-weight:bold;\n    cursor:pointer;\n}\n\nbutton:active{\n    transform:scale(.92);\n}\n\n.move{\n    font-size:25px;\n}\n\n.controls button:nth-child(1){\n    grid-column:2;\n}\n\n.controls button:nth-child(2){\n    grid-column:1;\n    grid-row:2;\n}\n\n.controls button:nth-child(3){\n    grid-column:2;\n    grid-row:2;\n}\n\n.controls button:nth-child(4){\n    grid-column:3;\n    grid-row:2;\n}\n\n#actions{\n    display:flex;\n    justify-content:center;\n    flex-wrap:wrap;\n    gap:7px;\n    padding:10px;\n}\n\n.action{\n    padding:11px 15px;\n}\n\n.overlay,\n#cutscene{\n    position:fixed;\n    inset:0;\n    z-index:20;\n    display:none;\n    align-items:center;\n    justify-content:center;\n}\n\n.overlay{\n    background:rgba(0,0,0,.94);\n}\n\n.panel{\n    width:min(90%,550px);\n    padding:25px;\n    text-align:center;\n    background:#160c08;\n    border:3px solid #a9652e;\n    border-radius:15px;\n}\n\n.panel h2{\n    color:#ffd45a;\n}\n\ninput{\n    padding:10px;\n    background:#050505;\n    color:white;\n    border:1px solid #a9652e;\n}\n\n#leaderboard{\n    margin-top:15px;\n    padding:10px;\n    text-align:left;\n    background:#050505;\n}\n\n#cutscene{\n    background:radial-gradient(circle,#641a0e,#100000 60%,#000);\n}\n\n#sceneText{\n    position:absolute;\n    bottom:20px;\n    left:5%;\n    right:5%;\n    padding:18px;\n    text-align:center;\n    background:rgba(0,0,0,.9);\n    border:2px solid #ad692d;\n    border-radius:10px;\n}\n\n#speaker{\n    color:#ffd45a;\n    font-size:20px;\n    font-weight:bold;\n}\n\n#words{\n    margin:8px;\n    font-size:17px;\n}\n\n#heroScene{\n    position:absolute;\n    left:7%;\n    bottom:170px;\n    width:100px;\n    height:210px;\n    transition:2s;\n}\n\n#dragonScene{\n    position:absolute;\n    right:4%;\n    bottom:160px;\n    width:300px;\n    height:260px;\n    transition:2s;\n}\n\n.heroHead{\n    position:absolute;\n    left:30px;\n    top:10px;\n    width:45px;\n    height:45px;\n    border-radius:50%;\n    background:#d99670;\n}\n\n.heroHair{\n    position:absolute;\n    left:25px;\n    top:4px;\n    width:55px;\n    height:28px;\n    border-radius:50%;\n    background:#17213b;\n}\n\n.heroBody{\n    position:absolute;\n    left:17px;\n    top:55px;\n    width:70px;\n    height:125px;\n    background:linear-gradient(90deg,#122957,#4b86db,#122957);\n    clip-path:polygon(20% 0,80% 0,100% 100%,0 100%);\n}\n\n.heroSword{\n    position:absolute;\n    right:0;\n    top:55px;\n    width:7px;\n    height:140px;\n    background:#eee;\n    transform:rotate(28deg);\n}\n\n.dBody{\n    position:absolute;\n    left:75px;\n    top:100px;\n    width:200px;\n    height:130px;\n    background:linear-gradient(135deg,#4e0704,#d42b18,#4d0504);\n    border-radius:60%;\n}\n\n.dHead{\n    position:absolute;\n    left:15px;\n    top:80px;\n    width:115px;\n    height:95px;\n    background:#b92116;\n    border-radius:60%;\n}\n\n.dEye{\n    position:absolute;\n    left:92px;\n    top:108px;\n    width:13px;\n    height:13px;\n    background:#fff500;\n    border-radius:50%;\n    box-shadow:0 0 15px yellow;\n}\n\n.wing{\n    position:absolute;\n    width:170px;\n    height:190px;\n    background:#350505;\n    clip-path:polygon(50% 0,100% 100%,55% 75%,0 100%);\n}\n\n.w1{\n    left:80px;\n    top:5px;\n}\n\n.w2{\n    right:0;\n    top:5px;\n    transform:scaleX(-1);\n}\n\n@media(max-width:600px){\n    #stats{\n        font-size:11px;\n        gap:6px;\n    }\n\n    .controls{\n        grid-template-columns:70px 70px 70px;\n        grid-template-rows:70px 70px;\n    }\n\n    #dragonScene{\n        transform:scale(.6);\n        transform-origin:right bottom;\n    }\n\n    #heroScene{\n        transform:scale(.7);\n        transform-origin:left bottom;\n    }\n}\n<\/style>\n\n\n\n\n<div id=\"game\">\n\n<h1>\ud83d\udc09 DRAGON&#8217;S LAIR: ULTIMATE<\/h1>\n\n<div id=\"stats\">\n<span>\u2764\ufe0f <b id=\"hp\">100\/100<\/b><\/span>\n<span>\u2694\ufe0f <b id=\"atk\">10<\/b><\/span>\n<span>\u2b50 LV <b id=\"level\">1<\/b><\/span>\n<span>\u2728 XP <b id=\"xp\">0\/100<\/b><\/span>\n<span>\ud83e\ude99 <b id=\"gold\">0<\/b><\/span>\n<span>\u2620\ufe0f <b id=\"kills\">0<\/b><\/span>\n<span>\ud83c\udfc6 <b id=\"score\">0<\/b><\/span>\n<span>\ud83e\uddea <b id=\"potions\">2<\/b><\/span>\n<span>\ud83d\udde1\ufe0f <b id=\"weapon\">Rusty Sword<\/b><\/span>\n<\/div>\n\n<div id=\"boss\">\n<div id=\"bossName\">\ud83d\udc09 ANCIENT RED DRAGON<\/div>\n<div id=\"bossBar\">\n<div id=\"bossHP\"><\/div>\n<\/div>\n<\/div>\n\n<canvas id=\"canvas\" width=\"900\" height=\"540\"><\/canvas>\n\n<div id=\"log\"><\/div>\n\n<div class=\"controls\">\n<button class=\"move\" onclick=\"move(0,-1)\">\u25b2<\/button>\n<button class=\"move\" onclick=\"move(-1,0)\">\u25c0<\/button>\n<button class=\"move\" onclick=\"move(0,1)\">\u25bc<\/button>\n<button class=\"move\" onclick=\"move(1,0)\">\u25b6<\/button>\n<\/div>\n\n<div id=\"actions\">\n<button class=\"action\" onclick=\"attackNearby()\">\u2694\ufe0f ATTACK<\/button>\n<button class=\"action\" onclick=\"heal()\">\ud83e\uddea HEAL<\/button>\n<button class=\"action\" onclick=\"toggleSound()\" id=\"soundButton\">\ud83d\udd0a SOUND<\/button>\n<button class=\"action\" onclick=\"location.reload()\">\u21bb RESTART<\/button>\n<\/div>\n\n<\/div>\n\n<div id=\"overlay\" class=\"overlay\">\n<div class=\"panel\">\n\n<h2 id=\"endTitle\"><\/h2>\n\n<p id=\"endText\"><\/p>\n\n<p>\n\u2620\ufe0f Kills: <b id=\"finalKills\"><\/b><br>\n\ud83e\ude99 Gold: <b id=\"finalGold\"><\/b><br>\n\ud83c\udfc6 Score: <b id=\"finalScore\"><\/b>\n<\/p>\n\n<input id=\"nameInput\" maxlength=\"16\" placeholder=\"Your name\">\n\n<br><br>\n\n<button onclick=\"saveScore()\">SAVE SCORE<\/button>\n<button onclick=\"location.reload()\">PLAY AGAIN<\/button>\n\n<div id=\"leaderboard\"><\/div>\n\n<\/div>\n<\/div>\n\n<div id=\"cutscene\">\n\n<div id=\"heroScene\">\n<div class=\"heroHead\"><\/div>\n<div class=\"heroHair\"><\/div>\n<div class=\"heroBody\"><\/div>\n<div class=\"heroSword\"><\/div>\n<\/div>\n\n<div id=\"dragonScene\">\n<div class=\"wing w1\"><\/div>\n<div class=\"wing w2\"><\/div>\n<div class=\"dBody\"><\/div>\n<div class=\"dHead\"><\/div>\n<div class=\"dEye\"><\/div>\n<\/div>\n\n<div id=\"sceneText\">\n\n<div id=\"speaker\"><\/div>\n<div id=\"words\"><\/div>\n\n<button onclick=\"finishCutscene()\">\nSKIP CUTSCENE\n<\/button>\n\n<\/div>\n<\/div>\n\n<script>\n\n\"use strict\";\n\nconst canvas=document.getElementById(\"canvas\");\nconst ctx=canvas.getContext(\"2d\");\n\nconst TILE=45;\nconst COLS=20;\nconst ROWS=12;\n\nlet map=[];\nlet enemies=[];\nlet items=[];\nlet particles=[];\n\nlet kills=0;\nlet score=0;\n\nlet bossSpawned=false;\nlet gameOver=false;\nlet ending=false;\nlet sound=true;\nlet audio=null;\n\nconst player={\n    x:2,\n    y:2,\n\n    hp:100,\n    maxHp:100,\n\n    attack:10,\n\n    level:1,\n    xp:0,\n    xpNext:100,\n\n    gold:0,\n    potions:2,\n\n    weaponBonus:0,\n    weapon:\"Rusty Sword\",\n\n    \/*\n       PERMANENT OBLITERATOR\n    *\/\n    obliterator:false,\n\n    invincible:0\n};\n\n\n\/* =========================\n   SOUND\n========================= *\/\n\nfunction startAudio(){\n\n    if(!sound)return;\n\n    if(!audio){\n        audio=new(\n            window.AudioContext||\n            window.webkitAudioContext\n        )();\n    }\n\n    if(audio.state===\"suspended\"){\n        audio.resume();\n    }\n}\n\nfunction tone(\n    freq,\n    duration,\n    type=\"square\",\n    volume=.04\n){\n\n    if(!audio||!sound)return;\n\n    const o=audio.createOscillator();\n    const g=audio.createGain();\n\n    o.type=type;\n    o.frequency.value=freq;\n\n    g.gain.setValueAtTime(\n        volume,\n        audio.currentTime\n    );\n\n    g.gain.exponentialRampToValueAtTime(\n        .001,\n        audio.currentTime+duration\n    );\n\n    o.connect(g);\n    g.connect(audio.destination);\n\n    o.start();\n    o.stop(\n        audio.currentTime+duration\n    );\n}\n\nfunction hitSound(){\n    tone(110,.08,\"sawtooth\",.06);\n}\n\nfunction pickupSound(){\n    tone(700,.08);\n    setTimeout(\n        ()=>tone(1000,.1),\n        80\n    );\n}\n\nfunction healSound(){\n    tone(500,.12,\"sine\");\n    setTimeout(\n        ()=>tone(750,.15,\"sine\"),\n        100\n    );\n}\n\nfunction roar(){\n    tone(40,.55,\"sawtooth\",.08);\n\n    setTimeout(\n        ()=>tone(\n            60,\n            .5,\n            \"sawtooth\",\n            .07\n        ),\n        200\n    );\n}\n\nfunction victoryMusic(){\n\n    [523,659,784,1046,1318]\n    .forEach((n,i)=>{\n        setTimeout(\n            ()=>tone(\n                n,\n                .35,\n                \"sine\",\n                .055\n            ),\n            i*170\n        );\n    });\n}\n\nfunction toggleSound(){\n\n    sound=!sound;\n\n    document.getElementById(\n        \"soundButton\"\n    ).textContent=\n        sound?\"\ud83d\udd0a SOUND\":\"\ud83d\udd07 SOUND\";\n\n    if(sound)startAudio();\n}\n\n\n\/* =========================\n   MAP\n========================= *\/\n\nfunction createMap(){\n\n    map=[];\n\n    for(let y=0;y<ROWS;y++){\n\n        map[y]=[];\n\n        for(let x=0;x<COLS;x++){\n\n            map[y][x]=\n                x===0||\n                y===0||\n                x===COLS-1||\n                y===ROWS-1\n                ?1:0;\n        }\n    }\n\n    [\n        [5,3],[5,4],[5,5],\n        [14,3],[14,4],[14,5],\n        [7,8],[8,8],\n        [11,7],[12,7],\n        [3,7],[17,4]\n    ].forEach(p=>{\n        map[p[1]][p[0]]=1;\n    });\n}\n\nfunction walkable(x,y){\n\n    return(\n        x>0&&\n        y>0&&\n        x<COLS-1&#038;&#038;\n        y<ROWS-1&#038;&#038;\n        map[y][x]===0\n    );\n}\n\n\n\/* =========================\n   ENEMIES\n========================= *\/\n\nfunction createEnemy(type,x,y){\n\n    const difficulty=1+kills*.075;\n\n    if(type===\"goblin\"){\n\n        return{\n            type,\n            x,y,\n            hp:Math.floor(35*difficulty),\n            maxHp:Math.floor(35*difficulty),\n            attack:Math.floor(7+kills*.28),\n            vision:8,\n            name:\"Goblin Hunter\",\n            color:\"#58c84f\"\n        };\n    }\n\n    if(type===\"orc\"){\n\n        return{\n            type,\n            x,y,\n            hp:Math.floor(70*difficulty),\n            maxHp:Math.floor(70*difficulty),\n            attack:Math.floor(12+kills*.38),\n            vision:9,\n            name:\"Orc Brute\",\n            color:\"#93a944\"\n        };\n    }\n\n    \/*\n       NEW ENEMY:\n       DARK KNIGHT\n    *\/\n\n    return{\n        type:\"darkknight\",\n        x,y,\n\n        hp:Math.floor(125*difficulty),\n        maxHp:Math.floor(125*difficulty),\n\n        attack:Math.floor(\n            20+kills*.5\n        ),\n\n        vision:11,\n\n        name:\"Dark Knight\",\n\n        color:\"#242b3b\",\n\n        armor:8\n    };\n}\n\n\n\/* =========================\n   FREE POSITION\n========================= *\/\n\nfunction freePosition(){\n\n    const spots=[];\n\n    for(let y=1;y<ROWS-1;y++){\n\n        for(let x=1;x<COLS-1;x++){\n\n            if(!walkable(x,y))\n                continue;\n\n            if(\n                Math.abs(x-player.x)+\n                Math.abs(y-player.y)<5\n            )\n                continue;\n\n            if(\n                enemies.some(\n                    e=>\n                        e.x===x&&\n                        e.y===y\n                )\n            )\n                continue;\n\n            if(\n                items.some(\n                    i=>\n                        i.x===x&&\n                        i.y===y\n                )\n            )\n                continue;\n\n            spots.push({x,y});\n        }\n    }\n\n    return spots.length\n        ?spots[\n            Math.floor(\n                Math.random()*spots.length\n            )\n        ]\n        :null;\n}\n\n\n\/* =========================\n   INFINITE ENEMY SPAWNING\n========================= *\/\n\nfunction spawnEnemy(){\n\n    if(bossSpawned)return;\n\n    const p=freePosition();\n\n    if(!p)return;\n\n\n    \/*\n       25% HYDRA\n    *\/\n\n    if(Math.random()<.25){\n\n        const hp=\n            Math.floor(\n                180+kills*12\n            );\n\n        enemies.push({\n\n            type:\"hydra\",\n\n            x:p.x,\n            y:p.y,\n\n            hp,\n            maxHp:hp,\n\n            attack:\n                Math.floor(\n                    22+kills*.45\n                ),\n\n            vision:12,\n\n            name:\"Immortal Hydra\",\n\n            color:\"#9b35d6\",\n\n            immortal:true\n        });\n\n        log(\n            \"\ud83d\udc0d\ud83d\udc0d\ud83d\udc0d THE IMMORTAL HYDRA HAS SPAWNED!\",\n            \"#d94cff\"\n        );\n\n        roar();\n\n        return;\n    }\n\n\n    \/*\n       NORMAL ENEMY ROLL\n    *\/\n\n    const r=Math.random();\n\n    const type=\n        r<.4\n        ?\"goblin\"\n        :r<.7\n        ?\"orc\"\n        :\"darkknight\";\n\n    const enemy=\n        createEnemy(\n            type,\n            p.x,\n            p.y\n        );\n\n    enemies.push(enemy);\n\n    if(type===\"darkknight\"){\n\n        log(\n            \"\u2694\ufe0f THE DARK KNIGHT HAS SPAWNED!\",\n            \"#8798c7\"\n        );\n\n    }else{\n\n        log(\n            \"\u26a0\ufe0f \"+\n            enemy.name+\n            \" has spawned!\",\n            \"#ff984d\"\n        );\n    }\n}\n\n\n\/* =========================\n   ITEMS\n========================= *\/\n\nfunction spawnItem(){\n\n    const p=freePosition();\n\n    if(!p)return;\n\n\n    \/*\n       75% NORMAL ITEM\n       25% OBLITERATOR WEAPON\n    *\/\n\n    if(Math.random()<.75){\n\n        const roll=Math.random();\n\n        if(roll<.4){\n\n            items.push({\n                type:\"potion\",\n                x:p.x,\n                y:p.y\n            });\n\n            log(\n                \"\ud83e\uddea A potion appeared!\",\n                \"#65ff77\"\n            );\n\n        }else if(roll<.7){\n\n            const amount=\n                25+\n                Math.floor(\n                    Math.random()*125\n                );\n\n            items.push({\n                type:\"gold\",\n                x:p.x,\n                y:p.y,\n                amount\n            });\n\n            log(\n                \"\ud83e\ude99 \"+amount+\n                \" gold appeared!\",\n                \"#ffd34f\"\n            );\n\n        }else{\n\n            const weapons=[\n                [\"Iron Sword\",8],\n                [\"Steel Sword\",16],\n                [\"Knight Blade\",27],\n                [\"Flame Sword\",42],\n                [\"Demon Blade\",60],\n                [\"Dragon Slayer\",85]\n            ];\n\n            const index=Math.min(\n                weapons.length-1,\n                Math.floor(kills\/4)\n            );\n\n            items.push({\n\n                type:\"weapon\",\n\n                x:p.x,\n                y:p.y,\n\n                name:\n                    weapons[index][0],\n\n                bonus:\n                    weapons[index][1]\n            });\n\n            log(\n                \"\u2694\ufe0f \"+\n                weapons[index][0]+\n                \" appeared!\",\n                \"#5beaff\"\n            );\n        }\n\n    }else{\n\n        items.push({\n\n            type:\"obliterator\",\n\n            x:p.x,\n            y:p.y,\n\n            name:\n                \"ONE-HIT OBLITERATOR\",\n\n            bonus:9999\n        });\n\n        log(\n            \"\ud83d\udc80\u26a1 ONE-HIT OBLITERATOR SPAWNED!\",\n            \"#ff00ff\"\n        );\n    }\n}\n\n\n\/* =========================\n   DRAGON\n========================= *\/\n\nfunction spawnDragon(){\n\n    if(bossSpawned)return;\n\n    bossSpawned=true;\n\n    const p=\n        freePosition()||\n        {x:10,y:2};\n\n    enemies.push({\n\n        boss:true,\n\n        type:\"dragon\",\n\n        x:p.x,\n        y:p.y,\n\n        hp:700,\n        maxHp:700,\n\n        attack:30,\n\n        vision:99,\n\n        phase:1,\n\n        name:\"Ancient Red Dragon\"\n    });\n\n    document.getElementById(\n        \"boss\"\n    ).style.display=\"block\";\n\n    log(\n        \"\ud83d\udc09 THE ANCIENT RED DRAGON HAS AWAKENED!\",\n        \"#ff3020\"\n    );\n\n    roar();\n}\n\n\n\/* =========================\n   LOG\n========================= *\/\n\nfunction log(text,color=\"#aaa\"){\n\n    const line=\n        document.createElement(\"div\");\n\n    line.textContent=text;\n    line.style.color=color;\n\n    document.getElementById(\n        \"log\"\n    ).appendChild(line);\n\n    const box=\n        document.getElementById(\"log\");\n\n    box.scrollTop=\n        box.scrollHeight;\n}\n\n\n\/* =========================\n   PARTICLES\n========================= *\/\n\nfunction burst(x,y,color){\n\n    for(let i=0;i<18;i++){\n\n        particles.push({\n\n            x:x*TILE+22,\n            y:y*TILE+22,\n\n            vx:(Math.random()-.5)*5,\n            vy:(Math.random()-.5)*5,\n\n            life:\n                25+\n                Math.random()*25,\n\n            color\n        });\n    }\n}\n\n\n\/* =========================\n   DRAW\n========================= *\/\n\nfunction draw(){\n\n    ctx.clearRect(\n        0,\n        0,\n        canvas.width,\n        canvas.height\n    );\n\n\n    \/*\n       FLOOR\n    *\/\n\n    for(let y=0;y<ROWS;y++){\n\n        for(let x=0;x<COLS;x++){\n\n            const px=x*TILE;\n            const py=y*TILE;\n\n            if(map[y][x]){\n\n                ctx.fillStyle=\"#3d2b27\";\n\n                ctx.fillRect(\n                    px,py,\n                    TILE,TILE\n                );\n\n                ctx.fillStyle=\"#5b4138\";\n\n                ctx.fillRect(\n                    px+4,\n                    py+4,\n                    TILE-8,\n                    6\n                );\n\n            }else{\n\n                ctx.fillStyle=\n                    (x+y)%2\n                    ?\"#10151a\"\n                    :\"#13191f\";\n\n                ctx.fillRect(\n                    px,py,\n                    TILE,TILE\n                );\n            }\n\n            ctx.strokeStyle=\"#20262d\";\n\n            ctx.strokeRect(\n                px,py,\n                TILE,TILE\n            );\n        }\n    }\n\n\n    \/*\n       ITEMS\n    *\/\n\n    items.forEach(i=>{\n\n        const x=\n            i.x*TILE+22;\n\n        const y=\n            i.y*TILE+22;\n\n\n        if(i.type===\"potion\"){\n\n            ctx.fillStyle=\"#b43be2\";\n\n            ctx.beginPath();\n\n            ctx.arc(\n                x,y+3,\n                11,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.fillStyle=\"#ddd\";\n\n            ctx.fillRect(\n                x-5,\n                y-11,\n                10,\n                7\n            );\n        }\n\n\n        if(i.type===\"gold\"){\n\n            ctx.fillStyle=\"#ffd43d\";\n\n            ctx.beginPath();\n\n            ctx.arc(\n                x,y,\n                11,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.fillStyle=\"#704900\";\n\n            ctx.font=\"bold 12px Arial\";\n\n            ctx.textAlign=\"center\";\n\n            ctx.fillText(\n                \"$\",\n                x,\n                y+4\n            );\n        }\n\n\n        if(i.type===\"weapon\"){\n\n            ctx.save();\n\n            ctx.translate(x,y);\n            ctx.rotate(-.7);\n\n            ctx.fillStyle=\"#eee\";\n\n            ctx.fillRect(\n                -3,\n                -18,\n                6,\n                31\n            );\n\n            ctx.fillStyle=\"#c4863c\";\n\n            ctx.fillRect(\n                -9,\n                8,\n                18,\n                5\n            );\n\n            ctx.fillStyle=\"#4b2718\";\n\n            ctx.fillRect(\n                -3,\n                13,\n                6,\n                10\n            );\n\n            ctx.restore();\n        }\n\n\n        \/*\n           OBLITERATOR\n        *\/\n\n        if(i.type===\"obliterator\"){\n\n            ctx.save();\n\n            ctx.translate(x,y);\n\n            ctx.rotate(\n                Math.sin(Date.now()\/150)*.15\n            );\n\n            ctx.shadowColor=\"#ff00ff\";\n            ctx.shadowBlur=25;\n\n            ctx.fillStyle=\"#ff00ff\";\n\n            ctx.beginPath();\n\n            for(let k=0;k<8;k++){\n\n                const a=\n                    k*Math.PI\/4;\n\n                const r=\n                    k%2?9:19;\n\n                ctx.lineTo(\n                    Math.cos(a)*r,\n                    Math.sin(a)*r\n                );\n            }\n\n            ctx.closePath();\n            ctx.fill();\n\n            ctx.shadowBlur=0;\n\n            ctx.fillStyle=\"#fff\";\n\n            ctx.beginPath();\n\n            ctx.arc(\n                0,\n                0,\n                6,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.restore();\n        }\n\n    });\n\n\n    \/*\n       ENEMIES\n    *\/\n\n    enemies.forEach(e=>{\n\n        const x=\n            e.x*TILE+22;\n\n        const y=\n            e.y*TILE+22;\n\n\n        \/*\n           DRAGON\n        *\/\n\n        if(e.boss){\n\n            ctx.shadowColor=\"#ff2100\";\n            ctx.shadowBlur=25;\n\n            ctx.fillStyle=\"#72140d\";\n\n            ctx.beginPath();\n\n            ctx.ellipse(\n                x,\n                y+5,\n                28,\n                34,\n                0,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.fillStyle=\"#c72b1d\";\n\n            ctx.beginPath();\n\n            ctx.arc(\n                x,\n                y-20,\n                21,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.fillStyle=\"#fff500\";\n\n            ctx.fillRect(\n                x-11,\n                y-24,\n                7,\n                5\n            );\n\n            ctx.fillRect(\n                x+4,\n                y-24,\n                7,\n                5\n            );\n\n            ctx.shadowBlur=0;\n\n            return;\n        }\n\n\n        \/*\n           IMMORTAL HYDRA\n        *\/\n\n        if(e.type===\"hydra\"){\n\n            ctx.save();\n\n            ctx.shadowColor=\"#c000ff\";\n            ctx.shadowBlur=25;\n\n            ctx.fillStyle=\"#42115e\";\n\n            ctx.beginPath();\n\n            ctx.ellipse(\n                x,\n                y+10,\n                24,\n                29,\n                0,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n\n            for(let h=-1;h<=1;h++){\n\n                const hx=\n                    x+h*15;\n\n                const hy=\n                    y-20-\n                    Math.abs(h)*5;\n\n                ctx.fillStyle=\n                    h===0\n                    ?\"#a938d4\"\n                    :\"#72249a\";\n\n                ctx.beginPath();\n\n                ctx.arc(\n                    hx,\n                    hy,\n                    14,\n                    0,\n                    Math.PI*2\n                );\n\n                ctx.fill();\n\n                ctx.fillStyle=\"#e3b5ff\";\n\n                ctx.beginPath();\n\n                ctx.moveTo(\n                    hx-7,\n                    hy-10\n                );\n\n                ctx.lineTo(\n                    hx-12,\n                    hy-23\n                );\n\n                ctx.lineTo(\n                    hx-2,\n                    hy-13\n                );\n\n                ctx.fill();\n\n                ctx.beginPath();\n\n                ctx.moveTo(\n                    hx+7,\n                    hy-10\n                );\n\n                ctx.lineTo(\n                    hx+12,\n                    hy-23\n                );\n\n                ctx.lineTo(\n                    hx+2,\n                    hy-13\n                );\n\n                ctx.fill();\n\n                ctx.fillStyle=\"#fff700\";\n\n                ctx.fillRect(\n                    hx-7,\n                    hy-3,\n                    5,\n                    5\n                );\n\n                ctx.fillRect(\n                    hx+3,\n                    hy-3,\n                    5,\n                    5\n                );\n            }\n\n            ctx.shadowBlur=0;\n\n            ctx.fillStyle=\"#30003d\";\n\n            ctx.fillRect(\n                e.x*TILE+2,\n                e.y*TILE-8,\n                40,\n                5\n            );\n\n            ctx.fillStyle=\"#d43cff\";\n\n            ctx.fillRect(\n                e.x*TILE+2,\n                e.y*TILE-8,\n                40*\n                Math.max(\n                    0,\n                    e.hp\/e.maxHp\n                ),\n                5\n            );\n\n            ctx.restore();\n\n            return;\n        }\n\n\n        \/*\n           DARK KNIGHT\n        *\/\n\n        if(e.type===\"darkknight\"){\n\n            ctx.save();\n\n            ctx.shadowColor=\"#536bff\";\n            ctx.shadowBlur=12;\n\n            \/*\n               Cape\n            *\/\n\n            ctx.fillStyle=\"#080a13\";\n\n            ctx.beginPath();\n\n            ctx.moveTo(\n                x-19,\n                y-2\n            );\n\n            ctx.lineTo(\n                x+19,\n                y-2\n            );\n\n            ctx.lineTo(\n                x+25,\n                y+28\n            );\n\n            ctx.lineTo(\n                x-25,\n                y+28\n            );\n\n            ctx.closePath();\n\n            ctx.fill();\n\n\n            \/*\n               Armor\n            *\/\n\n            ctx.fillStyle=\"#30384c\";\n\n            ctx.fillRect(\n                x-14,\n                y-3,\n                28,\n                30\n            );\n\n            ctx.fillStyle=\"#59677f\";\n\n            ctx.fillRect(\n                x-11,\n                y,\n                22,\n                6\n            );\n\n\n            \/*\n               Helmet\n            *\/\n\n            ctx.fillStyle=\"#1a2030\";\n\n            ctx.beginPath();\n\n            ctx.arc(\n                x,\n                y-13,\n                16,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n\n            \/*\n               Helmet horns\n            *\/\n\n            ctx.fillStyle=\"#111522\";\n\n            ctx.beginPath();\n\n            ctx.moveTo(\n                x-10,\n                y-23\n            );\n\n            ctx.lineTo(\n                x-19,\n                y-39\n            );\n\n            ctx.lineTo(\n                x-4,\n                y-27\n            );\n\n            ctx.fill();\n\n            ctx.beginPath();\n\n            ctx.moveTo(\n                x+10,\n                y-23\n            );\n\n            ctx.lineTo(\n                x+19,\n                y-39\n            );\n\n            ctx.lineTo(\n                x+4,\n                y-27\n            );\n\n            ctx.fill();\n\n\n            \/*\n               Glowing eyes\n            *\/\n\n            ctx.fillStyle=\"#55aaff\";\n\n            ctx.fillRect(\n                x-9,\n                y-15,\n                6,\n                4\n            );\n\n            ctx.fillRect(\n                x+3,\n                y-15,\n                6,\n                4\n            );\n\n\n            \/*\n               Huge sword\n            *\/\n\n            ctx.save();\n\n            ctx.translate(\n                x+19,\n                y+3\n            );\n\n            ctx.rotate(-.45);\n\n            ctx.fillStyle=\"#dce8ff\";\n\n            ctx.fillRect(\n                -4,\n                -30,\n                8,\n                42\n            );\n\n            ctx.fillStyle=\"#7888a8\";\n\n            ctx.fillRect(\n                -10,\n                9,\n                20,\n                5\n            );\n\n            ctx.fillStyle=\"#24190f\";\n\n            ctx.fillRect(\n                -3,\n                14,\n                6,\n                13\n            );\n\n            ctx.restore();\n\n            ctx.shadowBlur=0;\n\n            ctx.restore();\n\n        }else{\n\n            \/*\n               GOBLIN \/ ORC\n            *\/\n\n            ctx.fillStyle=e.color;\n\n            ctx.beginPath();\n\n            ctx.arc(\n                x,\n                y-6,\n                16,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.fillStyle=\"#111\";\n\n            ctx.beginPath();\n\n            ctx.arc(\n                x-6,\n                y-8,\n                3,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.beginPath();\n\n            ctx.arc(\n                x+6,\n                y-8,\n                3,\n                0,\n                Math.PI*2\n            );\n\n            ctx.fill();\n\n            ctx.fillStyle=\"#222\";\n\n            ctx.fillRect(\n                x-14,\n                y+8,\n                28,\n                17\n            );\n        }\n\n\n        \/*\n           HEALTH BAR\n        *\/\n\n        ctx.fillStyle=\"#300\";\n\n        ctx.fillRect(\n            e.x*TILE+3,\n            e.y*TILE-7,\n            39,\n            5\n        );\n\n        ctx.fillStyle=\n            e.type===\"darkknight\"\n            ?\" #5d7dff\"\n            :\"#39ee55\";\n\n        ctx.fillRect(\n            e.x*TILE+3,\n            e.y*TILE-7,\n            39*\n            Math.max(\n                0,\n                e.hp\/e.maxHp\n            ),\n            5\n        );\n    });\n\n\n    \/*\n       PLAYER\n    *\/\n\n    const px=\n        player.x*TILE+22;\n\n    const py=\n        player.y*TILE+22;\n\n    ctx.save();\n\n    if(\n        player.invincible>0&&\n        Math.floor(\n            player.invincible\/2\n        )%2===1\n    ){\n\n        ctx.globalAlpha=.4;\n    }\n\n    ctx.shadowColor=\"#3acbff\";\n    ctx.shadowBlur=17;\n\n    ctx.fillStyle=\"#294f9c\";\n\n    ctx.beginPath();\n\n    ctx.moveTo(\n        px-18,\n        py+24\n    );\n\n    ctx.lineTo(\n        px,\n        py-7\n    );\n\n    ctx.lineTo(\n        px+18,\n        py+24\n    );\n\n    ctx.closePath();\n\n    ctx.fill();\n\n    ctx.fillStyle=\"#386dcc\";\n\n    ctx.fillRect(\n        px-12,\n        py-2,\n        24,\n        30\n    );\n\n    ctx.fillStyle=\"#d9946c\";\n\n    ctx.beginPath();\n\n    ctx.arc(\n        px,\n        py-17,\n        10,\n        0,\n        Math.PI*2\n    );\n\n    ctx.fill();\n\n    ctx.fillStyle=\"#17213b\";\n\n    ctx.beginPath();\n\n    ctx.arc(\n        px,\n        py-20,\n        12,\n        Math.PI,\n        Math.PI*2\n    );\n\n    ctx.fill();\n\n\n    \/*\n       PLAYER SWORD\n    *\/\n\n    ctx.save();\n\n    ctx.translate(\n        px+14,\n        py+3\n    );\n\n    ctx.rotate(-.65);\n\n    ctx.fillStyle=\n        player.obliterator\n        ?\" #ff00ff\"\n        :\"#eee\";\n\n    ctx.fillRect(\n        -3,\n        -25,\n        6,\n        35\n    );\n\n    ctx.fillStyle=\"#c58b45\";\n\n    ctx.fillRect(\n        -9,\n        7,\n        18,\n        5\n    );\n\n    ctx.restore();\n\n    ctx.restore();\n\n\n    \/*\n       PARTICLES\n    *\/\n\n    particles.forEach(p=>{\n\n        ctx.globalAlpha=\n            Math.max(\n                0,\n                p.life\/40\n            );\n\n        ctx.fillStyle=p.color;\n\n        ctx.fillRect(\n            p.x,\n            p.y,\n            5,\n            5\n        );\n\n        p.x+=p.vx;\n        p.y+=p.vy;\n        p.vy+=.05;\n\n        p.life--;\n    });\n\n    ctx.globalAlpha=1;\n\n    particles=\n        particles.filter(\n            p=>p.life>0\n        );\n\n\n    updateBoss();\n}\n\n\n\/* =========================\n   UI\n========================= *\/\n\nfunction updateUI(){\n\n    document.getElementById(\n        \"hp\"\n    ).textContent=\n        Math.ceil(player.hp)+\n        \"\/\"+\n        player.maxHp;\n\n    document.getElementById(\n        \"atk\"\n    ).textContent=\n        player.attack+\n        player.weaponBonus;\n\n    document.getElementById(\n        \"level\"\n    ).textContent=\n        player.level;\n\n    document.getElementById(\n        \"xp\"\n    ).textContent=\n        player.xp+\n        \"\/\"+\n        player.xpNext;\n\n    document.getElementById(\n        \"gold\"\n    ).textContent=\n        player.gold;\n\n    document.getElementById(\n        \"kills\"\n    ).textContent=\n        kills;\n\n    document.getElementById(\n        \"potions\"\n    ).textContent=\n        player.potions;\n\n    document.getElementById(\n        \"weapon\"\n    ).textContent=\n        player.weapon;\n\n    score=\n        kills*150+\n        player.gold+\n        player.level*250+\n        player.weaponBonus*40+\n        player.xp;\n\n    document.getElementById(\n        \"score\"\n    ).textContent=\n        score;\n}\n\nfunction updateBoss(){\n\n    const boss=\n        enemies.find(\n            e=>e.boss\n        );\n\n    if(!boss)return;\n\n    document.getElementById(\n        \"bossHP\"\n    ).style.width=\n        Math.max(\n            0,\n            boss.hp\/boss.maxHp*100\n        )+\"%\";\n}\n\n\n\/* =========================\n   COLLECT\n========================= *\/\n\nfunction collect(){\n\n    const index=\n        items.findIndex(\n            i=>\n                i.x===player.x&&\n                i.y===player.y\n        );\n\n    if(index<0)return;\n\n    const item=items[index];\n\n\n    if(item.type===\"potion\"){\n\n        player.potions++;\n\n        log(\n            \"\ud83e\uddea Potion collected!\",\n            \"#65ff77\"\n        );\n\n        pickupSound();\n    }\n\n\n    if(item.type===\"gold\"){\n\n        player.gold+=\n            item.amount;\n\n        log(\n            \"\ud83e\ude99 +\"+\n            item.amount+\n            \" gold!\",\n            \"#ffd34f\"\n        );\n\n        pickupSound();\n    }\n\n\n    if(item.type===\"weapon\"){\n\n        if(\n            item.bonus>\n            player.weaponBonus\n        ){\n\n            player.weapon=\n                item.name;\n\n            player.weaponBonus=\n                item.bonus;\n\n            log(\n                \"\u2694\ufe0f Equipped \"+\n                item.name+\"!\",\n                \"#5beaff\"\n            );\n\n            pickupSound();\n\n        }else{\n\n            log(\n                \"\u2694\ufe0f Your current weapon is stronger.\",\n                \"#888\"\n            );\n        }\n    }\n\n\n    \/*\n       PERMANENT OBLITERATOR\n    *\/\n\n    if(item.type===\"obliterator\"){\n\n        player.obliterator=true;\n\n        player.weapon=\n            \"ONE-HIT OBLITERATOR\";\n\n        player.weaponBonus=9999;\n\n        log(\n            \"\ud83d\udc80\u26a1 ONE-HIT OBLITERATOR EQUIPPED!\",\n            \"#ff00ff\"\n        );\n\n        log(\n            \"It permanently destroys normal enemies in one hit!\",\n            \"#ff77ff\"\n        );\n\n        pickupSound();\n    }\n\n\n    items.splice(index,1);\n\n    updateUI();\n}\n\n\n\/* =========================\n   XP\n========================= *\/\n\nfunction gainXP(amount){\n\n    player.xp+=amount;\n\n    while(\n        player.xp>=\n        player.xpNext\n    ){\n\n        player.xp-=\n            player.xpNext;\n\n        player.level++;\n\n        player.xpNext=\n            Math.floor(\n                player.xpNext*1.25\n            );\n\n        player.maxHp+=15;\n\n        player.hp=\n            player.maxHp;\n\n        player.attack+=3;\n\n        log(\n            \"\u2b50 LEVEL UP! LEVEL \"+\n            player.level+\"!\",\n            \"#d987ff\"\n        );\n\n        tone(500,.12,\"sine\");\n\n        setTimeout(\n            ()=>tone(\n                800,\n                .18,\n                \"sine\"\n            ),\n            120\n        );\n    }\n}\n\n\n\/* =========================\n   PLAYER ATTACK\n========================= *\/\n\nfunction playerAttack(enemy){\n\n    startAudio();\n\n\n    \/*\n       OBLITERATOR\n       Does NOT work on\n       Hydra or Dragon.\n    *\/\n\n    if(\n        player.obliterator&&\n        !enemy.boss&&\n        !enemy.immortal\n    ){\n\n        enemy.hp=0;\n\n        log(\n            \"\ud83d\udc80\u26a1 OBLITERATOR!!! \"+\n            enemy.name+\n            \" WAS DESTROYED!\",\n            \"#ff00ff\"\n        );\n\n        burst(\n            enemy.x,\n            enemy.y,\n            \"#ff00ff\"\n        );\n\n        tone(\n            80,\n            .15,\n            \"sawtooth\",\n            .08\n        );\n\n        setTimeout(\n            ()=>defeat(enemy),\n            120\n        );\n\n        return;\n    }\n\n\n    \/*\n       DARK KNIGHT ARMOR\n    *\/\n\n    let damage=\n        player.attack+\n        player.weaponBonus+\n        Math.floor(\n            Math.random()*9\n        );\n\n    if(enemy.type===\"darkknight\"){\n\n        damage=\n            Math.max(\n                1,\n                damage-enemy.armor\n            );\n\n        log(\n            \"\ud83d\udee1\ufe0f The Dark Knight's armor absorbs damage!\",\n            \"#8395c9\"\n        );\n    }\n\n\n    enemy.hp-=damage;\n\n    hitSound();\n\n    burst(\n        enemy.x,\n        enemy.y,\n        enemy.boss\n        ?\"#ff2600\"\n        :\"#ffffff\"\n    );\n\n    log(\n        \"\u2694\ufe0f You hit \"+\n        enemy.name+\n        \" for \"+\n        damage+\n        \" damage.\",\n        \"#59eaff\"\n    );\n\n\n    if(enemy.hp<=0){\n\n        defeat(enemy);\n\n    }else{\n\n        enemyAttack(enemy);\n    }\n}\n\n\n\/* =========================\n   DEFEAT\n========================= *\/\n\nfunction defeat(enemy){\n\n    enemies=\n        enemies.filter(\n            e=>e!==enemy\n        );\n\n    kills++;\n\n    player.gold+=\n        enemy.boss\n        ?1000\n        :20;\n\n    gainXP(\n        enemy.boss\n        ?1000\n        :25+kills*2\n    );\n\n    burst(\n        enemy.x,\n        enemy.y,\n        enemy.boss\n        ?\" #ff2100\"\n        :\"#ffd34c\"\n    );\n\n    log(\n        \"\u2620\ufe0f \"+\n        enemy.name+\n        \" defeated!\",\n        \"#ffd34c\"\n    );\n\n\n    \/*\n       DRAGON DEFEATED\n    *\/\n\n    if(enemy.boss){\n\n        victoryMusic();\n\n        playEnding();\n\n        return;\n    }\n\n\n    \/*\n       EVERY KILL:\n       NEW ENEMY\n    *\/\n\n    spawnEnemy();\n\n\n    \/*\n       EVERY KILL:\n       ITEM ROLL\n    *\/\n\n    spawnItem();\n\n\n    \/*\n       DRAGON AFTER 10 KILLS\n    *\/\n\n    if(\n        kills>=10&&\n        !bossSpawned\n    ){\n\n        spawnDragon();\n    }\n\n    updateUI();\n    draw();\n}\n\n\n\/* =========================\n   ENEMY ATTACK\n========================= *\/\n\nfunction enemyAttack(enemy){\n\n    if(player.invincible>0)\n        return;\n\n    let damage;\n\n    if(enemy.boss){\n\n        damage=\n            enemy.attack+\n            Math.floor(\n                Math.random()*14\n            );\n\n        log(\n            \"\ud83d\udd25 DRAGON FIRE! -\"+\n            damage+\n            \" HP!\",\n            \"#ff5555\"\n        );\n\n    }else if(enemy.type===\"hydra\"){\n\n        damage=\n            enemy.attack+\n            Math.floor(\n                Math.random()*18\n            );\n\n        const attacks=[\n            \"\ud83d\udc0d Hydra bites you!\",\n            \"\ud83d\udc0d Hydra lashes you with its tail!\",\n            \"\ud83d\udc0d Hydra spits venom!\"\n        ];\n\n        log(\n            attacks[\n                Math.floor(\n                    Math.random()*\n                    attacks.length\n                )\n            ]+\n            \" -\"+\n            damage+\n            \" HP!\",\n            \"#d83cff\"\n        );\n\n    }else if(enemy.type===\"darkknight\"){\n\n        damage=\n            enemy.attack+\n            Math.floor(\n                Math.random()*10\n            );\n\n        log(\n            \"\u2694\ufe0f The Dark Knight strikes you for \"+\n            damage+\n            \" HP!\",\n            \"#8299d5\"\n        );\n\n    }else{\n\n        damage=\n            enemy.attack+\n            Math.floor(\n                Math.random()*7\n            );\n\n        log(\n            \"\ud83d\udca5 \"+\n            enemy.name+\n            \" hits you for \"+\n            damage+\n            \" HP!\",\n            \"#ff5555\"\n        );\n    }\n\n\n    player.hp=\n        Math.max(\n            0,\n            player.hp-damage\n        );\n\n    player.invincible=12;\n\n    hitSound();\n\n    burst(\n        player.x,\n        player.y,\n        \"#ff4444\"\n    );\n\n    if(player.hp<=0)\n        die();\n}\n\n\n\/* =========================\n   ENEMY AI\n========================= *\/\n\nfunction enemyAI(enemy){\n\n    if(gameOver)return;\n\n    const dx=\n        player.x-enemy.x;\n\n    const dy=\n        player.y-enemy.y;\n\n    const dist=\n        Math.abs(dx)+\n        Math.abs(dy);\n\n\n    if(dist<=1){\n\n        enemyAttack(enemy);\n\n        return;\n    }\n\n\n    \/*\n       DRAGON PHASES\n    *\/\n\n    if(enemy.boss){\n\n        if(\n            enemy.hp<\n            enemy.maxHp*.66&#038;&#038;\n            enemy.phase===1\n        ){\n\n            enemy.phase=2;\n            enemy.attack+=12;\n\n            log(\n                \"\ud83d\udd25 DRAGON PHASE TWO!\",\n                \"#ff2200\"\n            );\n\n            roar();\n        }\n\n        if(\n            enemy.hp<\n            enemy.maxHp*.33&#038;&#038;\n            enemy.phase===2\n        ){\n\n            enemy.phase=3;\n            enemy.attack+=18;\n\n            log(\n                \"\u2620\ufe0f FINAL DRAGON PHASE!\",\n                \"#ff2200\"\n            );\n\n            roar();\n        }\n    }\n\n\n    if(dist<=enemy.vision){\n\n        const moves=[];\n\n        if(\n            Math.abs(dx)>=\n            Math.abs(dy)\n        ){\n\n            if(dx)\n                moves.push([\n                    Math.sign(dx),\n                    0\n                ]);\n\n            if(dy)\n                moves.push([\n                    0,\n                    Math.sign(dy)\n                ]);\n\n        }else{\n\n            if(dy)\n                moves.push([\n                    0,\n                    Math.sign(dy)\n                ]);\n\n            if(dx)\n                moves.push([\n                    Math.sign(dx),\n                    0\n                ]);\n        }\n\n\n        for(const m of moves){\n\n            const nx=\n                enemy.x+m[0];\n\n            const ny=\n                enemy.y+m[1];\n\n            if(\n                walkable(nx,ny)&&\n                !(\n                    nx===player.x&&\n                    ny===player.y\n                )&&\n                !enemies.some(\n                    e=>\n                        e!==enemy&&\n                        e.x===nx&&\n                        e.y===ny\n                )\n            ){\n\n                enemy.x=nx;\n                enemy.y=ny;\n\n                return;\n            }\n        }\n\n    }else if(Math.random()<.3){\n\n        const dirs=[\n            [1,0],\n            [-1,0],\n            [0,1],\n            [0,-1]\n        ];\n\n        const d=\n            dirs[\n                Math.floor(\n                    Math.random()*\n                    dirs.length\n                )\n            ];\n\n        const nx=\n            enemy.x+d[0];\n\n        const ny=\n            enemy.y+d[1];\n\n        if(\n            walkable(nx,ny)&#038;&#038;\n            !enemies.some(\n                e=>\n                    e!==enemy&&\n                    e.x===nx&&\n                    e.y===ny\n            )\n        ){\n\n            enemy.x=nx;\n            enemy.y=ny;\n        }\n    }\n}\n\n\n\/* =========================\n   MOVE\n========================= *\/\n\nfunction move(dx,dy){\n\n    if(gameOver)return;\n\n    startAudio();\n\n    const nx=\n        player.x+dx;\n\n    const ny=\n        player.y+dy;\n\n    if(!walkable(nx,ny))\n        return;\n\n\n    const enemy=\n        enemies.find(\n            e=>\n                e.x===nx&&\n                e.y===ny\n        );\n\n\n    if(enemy){\n\n        playerAttack(enemy);\n\n    }else{\n\n        player.x=nx;\n        player.y=ny;\n\n        collect();\n\n        enemies\n            .slice()\n            .forEach(enemyAI);\n    }\n\n\n    if(player.invincible>0)\n        player.invincible--;\n\n    updateUI();\n    draw();\n}\n\n\n\/* =========================\n   ATTACK NEARBY\n========================= *\/\n\nfunction attackNearby(){\n\n    if(gameOver)return;\n\n    const target=\n        enemies.find(\n            e=>\n                Math.abs(\n                    e.x-player.x\n                )+\n                Math.abs(\n                    e.y-player.y\n                )<=1\n        );\n\n    if(target){\n\n        playerAttack(target);\n\n        draw();\n\n    }else{\n\n        log(\n            \"\u2694\ufe0f No enemy nearby.\",\n            \"#aaa\"\n        );\n    }\n}\n\n\n\/* =========================\n   HEAL\n========================= *\/\n\nfunction heal(){\n\n    if(gameOver)return;\n\n    startAudio();\n\n    if(player.potions<=0){\n\n        log(\n            \"\u274c No potions!\",\n            \"#ff5555\"\n        );\n\n        return;\n    }\n\n    if(player.hp>=player.maxHp){\n\n        log(\n            \"\u2764\ufe0f Your HP is already full.\",\n            \"#aaa\"\n        );\n\n        return;\n    }\n\n    player.potions--;\n\n    player.hp=\n        Math.min(\n            player.maxHp,\n            player.hp+\n            50+\n            player.level*6\n        );\n\n    healSound();\n\n    log(\n        \"\ud83e\uddea You healed!\",\n        \"#65ff77\"\n    );\n\n    updateUI();\n    draw();\n}\n\n\n\/* =========================\n   KEYBOARD\n========================= *\/\n\nwindow.addEventListener(\n    \"keydown\",\n    e=>{\n\n        if([\n            \"ArrowUp\",\n            \"ArrowDown\",\n            \"ArrowLeft\",\n            \"ArrowRight\",\n            \" \"\n        ].includes(e.key)){\n\n            e.preventDefault();\n        }\n\n        if(\n            e.key===\"ArrowUp\"||\n            e.key===\"w\"\n        )\n            move(0,-1);\n\n        if(\n            e.key===\"ArrowDown\"||\n            e.key===\"s\"\n        )\n            move(0,1);\n\n        if(\n            e.key===\"ArrowLeft\"||\n            e.key===\"a\"\n        )\n            move(-1,0);\n\n        if(\n            e.key===\"ArrowRight\"||\n            e.key===\"d\"\n        )\n            move(1,0);\n\n        if(e.key===\" \")\n            attackNearby();\n    }\n);\n\n\n\/* =========================\n   DEATH\n========================= *\/\n\nfunction die(){\n\n    if(gameOver)return;\n\n    gameOver=true;\n\n    showEnd(\n        \"\u2620\ufe0f YOU DIED\",\n        \"The dungeon defeated you.\"\n    );\n}\n\n\n\/* =========================\n   LEADERBOARD\n========================= *\/\n\nfunction getScores(){\n\n    try{\n\n        return JSON.parse(\n            localStorage.getItem(\n                \"dragonScores\"\n            )||\"[]\"\n        );\n\n    }catch{\n\n        return [];\n    }\n}\n\nfunction renderScores(){\n\n    const board=\n        document.getElementById(\n            \"leaderboard\"\n        );\n\n    const list=getScores();\n\n    board.innerHTML=\n        \"<h3>\ud83c\udfc6 HIGH SCORES<\/h3>\";\n\n    list.forEach(\n        (s,i)=>{\n\n            const d=\n                document.createElement(\n                    \"div\"\n                );\n\n            d.textContent=\n                (i+1)+\n                \". \"+\n                s.name+\n                \" \u2014 \"+\n                s.score;\n\n            board.appendChild(d);\n        }\n    );\n}\n\nfunction saveScore(){\n\n    const name=\n        (\n            document.getElementById(\n                \"nameInput\"\n            ).value.trim()||\n            \"Hero\"\n        ).slice(0,16);\n\n    const list=getScores();\n\n    list.push({\n        name,\n        score,\n        kills\n    });\n\n    list.sort(\n        (a,b)=>b.score-a.score\n    );\n\n    localStorage.setItem(\n        \"dragonScores\",\n        JSON.stringify(\n            list.slice(0,10)\n        )\n    );\n\n    renderScores();\n}\n\n\n\/* =========================\n   END SCREEN\n========================= *\/\n\nfunction showEnd(title,text){\n\n    document.getElementById(\n        \"endTitle\"\n    ).textContent=title;\n\n    document.getElementById(\n        \"endText\"\n    ).textContent=text;\n\n    document.getElementById(\n        \"finalKills\"\n    ).textContent=kills;\n\n    document.getElementById(\n        \"finalGold\"\n    ).textContent=player.gold;\n\n    document.getElementById(\n        \"finalScore\"\n    ).textContent=score;\n\n    document.getElementById(\n        \"overlay\"\n    ).style.display=\"flex\";\n\n    renderScores();\n}\n\n\n\/* =========================\n   CUTSCENE VOICES\n========================= *\/\n\nfunction speak(text,pitch=1){\n\n    if(!(\"speechSynthesis\" in window))\n        return;\n\n    speechSynthesis.cancel();\n\n    const v=\n        new SpeechSynthesisUtterance(\n            text\n        );\n\n    v.pitch=pitch;\n    v.rate=.9;\n\n    speechSynthesis.speak(v);\n}\n\nfunction wait(ms){\n\n    return new Promise(\n        resolve=>\n            setTimeout(resolve,ms)\n    );\n}\n\nasync function playEnding(){\n\n    if(ending)return;\n\n    ending=true;\n    gameOver=true;\n\n    const cut=\n        document.getElementById(\n            \"cutscene\"\n        );\n\n    const hero=\n        document.getElementById(\n            \"heroScene\"\n        );\n\n    const dragon=\n        document.getElementById(\n            \"dragonScene\"\n        );\n\n    const speaker=\n        document.getElementById(\n            \"speaker\"\n        );\n\n    const words=\n        document.getElementById(\n            \"words\"\n        );\n\n    cut.style.display=\"flex\";\n\n    speaker.textContent=\n        \"THE HERO\";\n\n    words.textContent=\n        \"The Ancient Red Dragon crashes to the ground.\";\n\n    await wait(1800);\n\n    hero.style.left=\"38%\";\n\n    speaker.textContent=\"HERO\";\n\n    words.textContent=\n        \"It's over.\";\n\n    speak(\n        \"It's over.\",\n        1.1\n    );\n\n    await wait(2200);\n\n    speaker.textContent=\"DRAGON\";\n\n    words.textContent=\n        \"You have defeated me, mortal.\";\n\n    speak(\n        \"You have defeated me, mortal.\",\n        .45\n    );\n\n    await wait(2500);\n\n    hero.style.left=\"48%\";\n\n    speaker.textContent=\"HERO\";\n\n    words.textContent=\n        \"Your reign ends here.\";\n\n    speak(\n        \"Your reign ends here.\",\n        1.1\n    );\n\n    await wait(2200);\n\n    speaker.textContent=\"DRAGON\";\n\n    words.textContent=\n        \"Take the treasure. You have earned it.\";\n\n    speak(\n        \"Take the treasure. You have earned it.\",\n        .45\n    );\n\n    await wait(2800);\n\n    dragon.style.transform=\"scale(.2)\";\n    dragon.style.opacity=\"0\";\n\n    speaker.textContent=\"THE END\";\n\n    words.textContent=\n        \"The dungeon is finally free.\";\n\n    victoryMusic();\n\n    await wait(3200);\n\n    finishCutscene();\n}\n\nfunction finishCutscene(){\n\n    document.getElementById(\n        \"cutscene\"\n    ).style.display=\"none\";\n\n    showEnd(\n        \"\ud83c\udfc6 DRAGON SLAYER!\",\n        \"You defeated the Ancient Red Dragon!\"\n    );\n}\n\n\n\/* =========================\n   GAME START\n========================= *\/\n\ncreateMap();\n\nenemies=[\n    createEnemy(\"goblin\",4,2),\n    createEnemy(\"orc\",16,2),\n    createEnemy(\"darkknight\",10,4),\n    createEnemy(\"goblin\",6,7)\n];\n\nitems=[\n    {\n        type:\"potion\",\n        x:2,\n        y:5\n    },\n    {\n        type:\"potion\",\n        x:17,\n        y:7\n    },\n    {\n        type:\"gold\",\n        x:9,\n        y:9,\n        amount:100\n    },\n    {\n        type:\"weapon\",\n        x:3,\n        y:9,\n        name:\"Iron Sword\",\n        bonus:8\n    }\n];\n\nlog(\n    \"\ud83d\udc09 DRAGON'S LAIR ULTIMATE\",\n    \"#ffd052\"\n);\n\nlog(\n    \"\u267e\ufe0f Every normal enemy you defeat spawns another!\",\n    \"#58eaff\"\n);\n\nlog(\n    \"\ud83d\udc0d 25% chance for the IMMORTAL HYDRA!\",\n    \"#d94cff\"\n);\n\nlog(\n    \"\u2694\ufe0f DARK KNIGHTS have armor and huge swords!\",\n    \"#8299d5\"\n);\n\nlog(\n    \"\ud83c\udf81 75% chance of an item!\",\n    \"#65ff77\"\n);\n\nlog(\n    \"\ud83d\udc80 25% chance of a permanent ONE-HIT OBLITERATOR!\",\n    \"#ff00ff\"\n);\n\nlog(\n    \"\ud83d\udc09 The Dragon appears after 10 kills!\",\n    \"#ff4b38\"\n);\n\nupdateUI();\ndraw();\n\n\n\/* =========================\n   ANIMATION LOOP\n========================= *\/\n\nfunction gameLoop(){\n\n    if(\n        !gameOver&&\n        !ending\n    ){\n\n        draw();\n    }\n\n    requestAnimationFrame(\n        gameLoop\n    );\n}\n\ngameLoop();\n\n<\/script>\n\n\n\n<script data-wp-block-html=\"js\">\n\/* ================================\n   ITEM \/ OBLITERATOR SPAWNING\n   ================================ *\/\n\nfunction spawnItem() {\n    const p = freePosition();\n    if (!p) return;\n\n    \/\/ 75% chance = normal item\n    \/\/ 25% chance = ONE-HIT OBLITERATOR\n    if (Math.random() < 0.75) {\n\n        const roll = Math.random();\n\n        if (roll < 0.40) {\n            items.push({\n                type: \"potion\",\n                x: p.x,\n                y: p.y\n            });\n\n            log(\"\ud83e\uddea A healing potion appeared!\", \"#65ff77\");\n\n        } else if (roll < 0.70) {\n\n            const amount =\n                25 + Math.floor(Math.random() * 125);\n\n            items.push({\n                type: \"gold\",\n                x: p.x,\n                y: p.y,\n                amount: amount\n            });\n\n            log(\n                \"\ud83e\ude99 A gold pile appeared! +\" + amount,\n                \"#ffd34f\"\n            );\n\n        } else {\n\n            const weapons = [\n                [\"Iron Sword\", 8],\n                [\"Steel Sword\", 16],\n                [\"Knight Blade\", 27],\n                [\"Flame Sword\", 42],\n                [\"Demon Blade\", 60],\n                [\"Dragon Slayer\", 85]\n            ];\n\n            const index = Math.min(\n                weapons.length - 1,\n                Math.floor(kills \/ 4)\n            );\n\n            items.push({\n                type: \"weapon\",\n                x: p.x,\n                y: p.y,\n                name: weapons[index][0],\n                bonus: weapons[index][1]\n            });\n\n            log(\n                \"\u2694\ufe0f A \" + weapons[index][0] +\n                \" appeared!\",\n                \"#5beaff\"\n            );\n        }\n\n    } else {\n\n        \/\/ 25% chance\n        items.push({\n            type: \"obliterator\",\n            x: p.x,\n            y: p.y\n        });\n\n        log(\n            \"\ud83d\udc80\u26a1 ONE-HIT OBLITERATOR HAS SPAWNED!\",\n            \"#ff00ff\"\n        );\n    }\n}\n\n\n\/* ================================\n   DEFEAT ENEMY\n   ================================ *\/\n\nfunction defeat(enemy) {\n\n    \/\/ Remove dead enemy\n    enemies = enemies.filter(e => e !== enemy);\n\n    kills++;\n\n    \/\/ Boss gives a huge reward\n    const reward = enemy.boss ? 1000 : 20;\n\n    player.gold += reward;\n\n    \/\/ XP\n    gainXP(\n        enemy.boss\n            ? 1000\n            : 25 + kills * 2\n    );\n\n    \/\/ Death particles\n    burst(\n        enemy.x,\n        enemy.y,\n        enemy.boss ? \"#ff2100\" : \"#ffd34c\"\n    );\n\n    log(\n        \"\u2620\ufe0f \" + enemy.name + \" defeated!\",\n        \"#ffd34c\"\n    );\n\n\n    \/* ================================\n       DRAGON DEFEATED\n       ================================ *\/\n\n    if (enemy.boss) {\n\n        victoryMusic();\n\n        playEnding();\n\n        return;\n    }\n\n\n    \/* ================================\n       ALWAYS SPAWN A NEW ENEMY\n       ================================ *\/\n\n    \/\/ This happens EVERY time a normal\n    \/\/ enemy is defeated.\n    spawnEnemy();\n\n\n    \/* ================================\n       75% ITEM \/ 25% OBLITERATOR\n       ================================ *\/\n\n    spawnItem();\n\n\n    \/* ================================\n       DRAGON AT 10 KILLS\n       ================================ *\/\n\n    if (kills >= 10 && !bossSpawned) {\n        spawnDragon();\n    }\n\n\n    updateUI();\n    draw();\n}\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>Dragon&#8217;s Lair Ultimate \ud83d\udc09 DRAGON&#8217;S LAIR: ULTIMATE \u2764\ufe0f 100\/100 \u2694\ufe0f 10 \u2b50 LV 1 \u2728 XP 0\/100 \ud83e\ude99 0 \u2620\ufe0f 0 \ud83c\udfc6 0 \ud83e\uddea 2 \ud83d\udde1\ufe0f Rusty Sword \ud83d\udc09 ANCIENT RED DRAGON \u25b2 \u25c0 \u25bc \u25b6 \u2694\ufe0f ATTACK \ud83e\uddea HEAL \ud83d\udd0a SOUND \u21bb RESTART \u2620\ufe0f Kills: \ud83e\ude99 Gold: \ud83c\udfc6 Score: SAVE SCORE PLAY AGAIN SKIP &#8230; <a title=\"Dragon&#8217;s Lair\" class=\"read-more\" href=\"https:\/\/ethannukoop.com\/?page_id=39\" aria-label=\"Read more about Dragon&#8217;s Lair\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-39","page","type-page","status-publish"],"featured_image_src":null,"featured_image_src_square":null,"_links":{"self":[{"href":"https:\/\/ethannukoop.com\/index.php?rest_route=\/wp\/v2\/pages\/39","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ethannukoop.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/ethannukoop.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/ethannukoop.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ethannukoop.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=39"}],"version-history":[{"count":2,"href":"https:\/\/ethannukoop.com\/index.php?rest_route=\/wp\/v2\/pages\/39\/revisions"}],"predecessor-version":[{"id":50,"href":"https:\/\/ethannukoop.com\/index.php?rest_route=\/wp\/v2\/pages\/39\/revisions\/50"}],"wp:attachment":[{"href":"https:\/\/ethannukoop.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=39"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}