2i RPG - Fantasy Adventure
by ShadowTurtle30388 lines14.5 KB
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>2i RPG - Fantasy Adventure</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#151d29;color:white;font-family:monospace;text-align:center}
header{padding:12px;color:#ffdc83;font-size:28px;letter-spacing:5px}
.panel{background:#27374a;margin:8px;padding:10px;border-radius:9px}
canvas{display:block;width:100%;max-width:800px;margin:auto;image-rendering:pixelated}
button,select{font:bold 14px monospace;padding:12px;background:#394f67;color:white;border:2px solid #8196aa;border-radius:7px;cursor:pointer}
button:active{background:#6887a3}
button:disabled{opacity:.45;cursor:not-allowed}
.row{display:flex;justify-content:center;align-items:center;gap:7px;flex-wrap:wrap;margin:8px 0}
.stats{font-size:12px;line-height:1.9}
#quest{color:#ffdf88}
#log{font-size:12px;color:#d2e4f5;min-height:32px;line-height:1.5}
#screen{position:relative;max-width:800px;margin:auto}
#overlay{position:absolute;inset:0;background:rgba(13,23,36,.95);display:flex;flex-direction:column;align-items:center;justify-content:center;padding:12px;z-index:2}
#overlay h2{color:#ffdf88;font-size:27px;margin:5px}
#overlay p{font-size:12px;line-height:1.7}
#classButtons{display:flex;gap:7px;flex-wrap:wrap;justify-content:center;margin:12px 0}
.classbtn.selected{border-color:#ffe58c;background:#785b32}
.big{font-size:19px;padding:14px 25px;background:#287748;border-color:#8ee9a5}
.retry{background:#8c483e;border-color:#ffb09e}
.skill{background:#6748a4;border-color:#c7a5ff}
</style>
</head>
<body>
<header>⚔ 2i RPG ⚔</header>
<div class="panel">
<div id="status">AVVENTURA IN SOLITARIA</div>
<div id="stats" class="stats"></div>
</div>
<div class="panel">
<b>📜 MISSIONE: LIBERA IL VILLAGGIO</b>
<p id="quest">Sconfiggi 5 mostri per salvare il villaggio!</p>
<div id="progress">Mostri sconfitti: 0/5</div>
</div>
<div id="screen">
<canvas id="game" width="800" height="400"></canvas>
<div id="overlay">
<h2 id="overlayTitle">⚔ 2i RPG ⚔</h2>
<p id="overlayText">Scegli la tua classe e preparati all'avventura!<br>Hai tutto il tempo che vuoi.</p>
<b>SCEGLI LA TUA CLASSE</b>
<div id="classButtons">
<button class="classbtn selected" data-class="warrior">🛡 Guerriero</button>
<button class="classbtn" data-class="mage">🔮 Mago</button>
<button class="classbtn" data-class="archer">🏹 Arciere</button>
</div>
<p id="classDescription">Molti punti vita e colpi potenti.</p>
<button id="play" class="big">▶ GIOCA</button>
<button id="retry" class="retry" style="display:none;margin-top:10px">↻ RIPROVA</button>
</div>
</div>
<div class="panel">
<div class="row">
<button id="left">◀ SINISTRA</button>
<button id="jump">⬆ SALTA</button>
<button id="right">DESTRA ▶</button>
</div>
<div class="row">
<button id="attack">⚔ ATTACCA [E]</button>
<button id="skill" class="skill">✨ ABILITÀ [Q]</button>
<button id="potion">🧪 POZIONE [R]</button>
</div>
<div id="log">Scegli la classe, poi premi GIOCA.</div>
</div>
<script>
const c=document.querySelector("#game"),ctx=c.getContext("2d");
const overlay=document.querySelector("#overlay");
const title=document.querySelector("#overlayTitle");
const overlayText=document.querySelector("#overlayText");
const stats=document.querySelector("#stats");
const status=document.querySelector("#status");
const quest=document.querySelector("#quest");
const progress=document.querySelector("#progress");
const log=document.querySelector("#log");
const floor=320,world=5000;
const classes={
warrior:{name:"Guerriero",hp:150,mana:50,damage:27,cost:15,
skill:"COLPO POTENTE",desc:"❤️ 150 vita · ⚔ Danni 27 · Attacco speciale potentissimo",color:"#f06464"},
mage:{name:"Mago",hp:95,mana:120,damage:17,cost:25,
skill:"PALLA DI FUOCO",desc:"🔮 120 mana · 🔥 Magia che danneggia più mostri",color:"#bd8bff"},
archer:{name:"Arciere",hp:115,mana:75,damage:21,cost:18,
skill:"FRECCIA MULTIPLA",desc:"🏹 Attacchi rapidi · Colpisce fino a 3 mostri",color:"#7de59a"}
};
let selectedClass="warrior",started=false,gameOver=false,done=false;
let defeated=0,xp=0,level=1,gold=0,potions=3,monsters=[],keys={};
let player={},nextId=0,lastTime=0,spawnClock=0;
function say(s){log.textContent=s}
function cls(){return classes[selectedClass]}
function resetGame(){
const a=cls();
player={x:100,y:floor-32,vy:0,ground:true,hp:a.hp,mana:a.mana,
damage:a.damage,color:a.color,invincible:0};
defeated=0;xp=0;level=1;gold=0;potions=3;
done=false;gameOver=false;monsters=[];nextId=0;spawnClock=0;
for(let i=0;i<8;i++)spawnMonster(300+i*180);
quest.textContent="Sconfiggi 5 mostri per salvare il villaggio!";
progress.textContent="Mostri sconfitti: 0/5";
updateStats();draw();
}
function updateStats(){
const a=cls();
stats.innerHTML="🛡 "+a.name+" · ⭐ Livello "+level+
" · XP "+xp+" · 🪙 Oro "+gold+"<br>"+
"❤️ Vita "+Math.max(0,player.hp)+"/"+a.hp+
" · 🔮 Mana "+player.mana+"/"+a.mana+
" · 🧪 Pozioni "+potions;
document.querySelector("#skill").textContent=
"✨ "+a.skill+" ["+a.cost+" mana]";
document.querySelector("#skill").disabled=
!started||gameOver||player.mana<a.cost;
document.querySelector("#attack").disabled=!started||gameOver;
document.querySelector("#potion").disabled=!started||gameOver||potions<=0;
}
function selectClass(name){
if(started)return;
selectedClass=name;
document.querySelectorAll(".classbtn").forEach(b=>
b.classList.toggle("selected",b.dataset.class===name));
document.querySelector("#classDescription").textContent=cls().desc;
player.color=cls().color;
player.hp=cls().hp;player.mana=cls().mana;
player.damage=cls().damage;
updateStats();draw();
}
document.querySelectorAll(".classbtn").forEach(b=>{
b.onclick=()=>selectClass(b.dataset.class);
});
function startGame(){
if(started&&!gameOver)return;
resetGame();
started=true;
overlay.style.display="none";
status.textContent="⚔ AVVENTURA IN CORSO · "+cls().name;
say("La missione comincia! Sconfiggi 5 mostri.");
updateStats();
}
document.querySelector("#play").onclick=startGame;
function showMenu(isRetry){
started=false;
overlay.style.display="flex";
document.querySelector("#play").style.display=isRetry?"none":"inline-block";
document.querySelector("#retry").style.display=isRetry?"inline-block":"none";
document.querySelectorAll(".classbtn").forEach(b=>b.disabled=isRetry);
title.textContent=isRetry?"💀 SEI STATO SCONFITTO":"⚔ 2i RPG ⚔";
overlayText.textContent=isRetry
?"Il tuo eroe è caduto. Premi RIPROVA per tentare di nuovo!"
:"Scegli la tua classe e preparati all'avventura! Hai tutto il tempo che vuoi.";
status.textContent=isRetry?"SCONFITTA":"PRONTO PER GIOCARE";
updateStats();draw();
}
document.querySelector("#retry").onclick=()=>{
document.querySelectorAll(".classbtn").forEach(b=>b.disabled=false);
startGame();
};
function spawnMonster(x){
const type=Math.random()<.65?"slime":"goblin";
monsters.push({id:nextId++,type,x,y:floor-32,
hp:type==="slime"?35:55,max:type==="slime"?35:55,
damage:type==="slime"?5:9,speed:type==="slime"?.55:.8,
alive:true});
}
function awardXP(n){
xp+=n;
while(xp>=level*100){
xp-=level*100;level++;
player.hp=Math.min(cls().hp,player.hp+25);
player.mana=Math.min(cls().mana,player.mana+20);
say("⭐ LIVELLO "+level+"! Hai recuperato vita e mana.");
}
}
function killMonster(m){
if(!m.alive)return;
m.alive=false;defeated++;gold+=5;
awardXP(m.type==="goblin"?35:20);
say("💥 Mostro sconfitto! +XP, +5 oro.");
if(defeated>=5&&!done){
done=true;gold+=50;awardXP(100);
quest.textContent="🏆 MISSIONE COMPLETATA! Il villaggio è salvo!";
say("🏆 VITTORIA! Hai liberato il villaggio. +50 oro e 100 XP!");
}
progress.textContent="Mostri sconfitti: "+defeated+"/5";
updateStats();
}
function attack(){
if(!started||gameOver)return;
const m=monsters.filter(m=>m.alive&&Math.abs(m.x-player.x)<90)
.sort((a,b)=>Math.abs(a.x-player.x)-Math.abs(b.x-player.x))[0];
if(!m){say("Nessun mostro vicino. Avvicinati!");return;}
m.hp-=player.damage;
say("⚔ Colpo riuscito! Danni: "+player.damage);
if(m.hp<=0)killMonster(m);
updateStats();
}
function special(){
if(!started||gameOver)return;
const a=cls();
if(player.mana<a.cost){say("🔮 Mana insufficiente!");return;}
const targets=monsters.filter(m=>m.alive&&Math.abs(m.x-player.x)<210)
.sort((a,b)=>Math.abs(a.x-player.x)-Math.abs(b.x-player.x));
if(!targets.length){say("Nessun bersaglio vicino per "+a.skill+"!");return;}
player.mana-=a.cost;
let list,damage;
if(selectedClass==="warrior"){list=targets.slice(0,1);damage=55;}
else if(selectedClass==="mage"){list=targets.slice(0,3);damage=40;}
else{list=targets.slice(0,3);damage=30;}
list.forEach(m=>{m.hp-=damage;if(m.hp<=0)killMonster(m)});
say("✨ "+a.skill+"! Hai inflitto "+damage+" danni a "+list.length+" bersagli.");
updateStats();
}
function potion(){
if(!started||gameOver)return;
if(potions<=0){say("Hai finito le pozioni!");return;}
if(player.hp>=cls().hp&&player.mana>=cls().mana){
say("Vita e mana sono già al massimo!");return;
}
potions--;
player.hp=Math.min(cls().hp,player.hp+45);
player.mana=Math.min(cls().mana,player.mana+30);
say("🧪 Pozione utilizzata: vita e mana recuperati.");
updateStats();
}
document.querySelector("#attack").onclick=attack;
document.querySelector("#skill").onclick=special;
document.querySelector("#potion").onclick=potion;
function jump(){
if(started&&!gameOver&&player.ground){
player.vy=-10;player.ground=false;
}
}
document.querySelector("#jump").onclick=jump;
function bindMove(id,key){
const b=document.querySelector("#"+id);
b.onpointerdown=e=>{e.preventDefault();keys[key]=true};
["pointerup","pointerleave","pointercancel"].forEach(ev=>
b.addEventListener(ev,()=>keys[key]=false));
}
bindMove("left","left");bindMove("right","right");
window.addEventListener("keydown",e=>{
const k=e.key.toLowerCase();
if([" ","arrowleft","arrowright","arrowup"].includes(k))e.preventDefault();
keys[k]=true;
if((k===" "||k==="arrowup")&&!e.repeat)jump();
if(k==="e"&&!e.repeat)attack();
if(k==="q"&&!e.repeat)special();
if(k==="r"&&!e.repeat)potion();
});
window.addEventListener("keyup",e=>keys[e.key.toLowerCase()]=false);
function draw(){
const camera=Math.max(0,Math.min(world-800,(player.x||0)-350));
const sky=ctx.createLinearGradient(0,0,0,400);
sky.addColorStop(0,"#72bce6");sky.addColorStop(1,"#d5e9c2");
ctx.fillStyle=sky;ctx.fillRect(0,0,800,400);
ctx.fillStyle="#ffeb9b";ctx.fillRect(670,40,42,42);
for(let x=Math.floor(camera/32);x<(camera+800)/32+1;x++){
let sx=x*32-camera;
ctx.fillStyle="#6bb64b";ctx.fillRect(sx,floor,32,12);
ctx.fillStyle="#79533a";ctx.fillRect(sx,floor+12,32,100);
ctx.strokeStyle="#60432f";ctx.strokeRect(sx,floor+12,32,100);
}
for(let x=160;x<world;x+=230){
const sx=x-camera;if(sx<-60||sx>860)continue;
ctx.fillStyle="#69452e";ctx.fillRect(sx,floor-48,15,48);
ctx.fillStyle="#347d43";ctx.fillRect(sx-18,floor-88,52,42);
ctx.fillStyle="#4a9a4c";ctx.fillRect(sx-8,floor-104,32,20);
}
const vx=100-camera;
if(vx>-110&&vx<810){
ctx.fillStyle="#d8bd85";ctx.fillRect(vx,floor-65,85,65);
ctx.fillStyle="#91563e";ctx.fillRect(vx-8,floor-80,101,20);
ctx.fillStyle="#634630";ctx.fillRect(vx+33,floor-33,20,33);
ctx.fillStyle="white";ctx.font="11px monospace";
ctx.fillText("VILLAGGIO",vx-5,floor-91);
}
for(const m of monsters){
if(!m.alive)continue;
const x=m.x-camera;if(x<-60||x>860)continue;
if(m.type==="slime"){
ctx.fillStyle="#4ed568";ctx.fillRect(x,m.y+8,30,24);
ctx.fillStyle="#8af58e";ctx.fillRect(x+5,m.y+2,20,12);
ctx.fillStyle="#173a29";ctx.fillRect(x+6,m.y+14,5,5);ctx.fillRect(x+20,m.y+14,5,5);
}else{
ctx.fillStyle="#53864a";ctx.fillRect(x+3,m.y+3,24,29);
ctx.fillStyle="#7da85a";ctx.fillRect(x+5,m.y-2,20,16);
ctx.fillStyle="#ffef9a";ctx.fillRect(x+8,m.y+4,4,4);ctx.fillRect(x+19,m.y+4,4,4);
}
ctx.fillStyle="#35212a";ctx.fillRect(x,m.y-13,34,6);
ctx.fillStyle="#e95050";ctx.fillRect(x,m.y-13,34*Math.max(0,m.hp/m.max),6);
ctx.fillStyle="white";ctx.font="10px monospace";
ctx.fillText(m.type==="slime"?"SLIME":"GOBLIN",x-2,m.y-18);
}
const px=(player.x||0)-camera,py=player.y||floor-32;
if(player.invincible<=0||Math.floor(performance.now()/100)%2===0){
ctx.fillStyle=player.color||"#ed6464";ctx.fillRect(px,py+10,24,22);
ctx.fillStyle="#efc39b";ctx.fillRect(px+3,py,18,16);
ctx.fillStyle="#553b2d";ctx.fillRect(px+2,py,20,6);
ctx.fillStyle="white";ctx.fillRect(px+5,py+6,4,4);ctx.fillRect(px+15,py+6,4,4);
}
ctx.fillStyle="#173044";ctx.font="12px monospace";
ctx.fillText("2i RPG · X: "+Math.floor(player.x||0),12,20);
}
function update(t){
const dt=Math.min(2,(t-lastTime)/16.67||1);lastTime=t;
if(started&&!gameOver){
const dx=((keys.right||keys.d||keys.arrowright)?1:0)-
((keys.left||keys.a||keys.arrowleft)?1:0);
player.x=Math.max(0,Math.min(world-24,player.x+dx*3*dt));
player.vy=Math.min(12,player.vy+.5*dt);
player.y+=player.vy*dt;
if(player.y+32>=floor){
player.y=floor-32;player.vy=0;player.ground=true;
}
for(const m of monsters){
if(!m.alive)continue;
const d=Math.abs(m.x-player.x);
if(d<350)m.x+=Math.sign(player.x-m.x)*m.speed*dt;
if(d<27&&player.invincible<=0){
player.hp=Math.max(0,player.hp-m.damage);
player.invincible=55;
say("💥 Un mostro ti ha colpito!");
updateStats();
}
}
if(player.invincible>0)player.invincible-=dt;
spawnClock+=dt;
if(spawnClock>180&&monsters.filter(m=>m.alive).length<7){
spawnMonster(player.x+350+Math.random()*300);spawnClock=0;
}
if(player.hp<=0){
gameOver=true;started=false;
showMenu(true);
}
}
draw();
requestAnimationFrame(update);
}
function showMenu(isRetry){
overlay.style.display="flex";
document.querySelector("#play").style.display=isRetry?"none":"inline-block";
document.querySelector("#retry").style.display=isRetry?"inline-block":"none";
document.querySelectorAll(".classbtn").forEach(b=>b.disabled=isRetry);
title.textContent=isRetry?"💀 SEI STATO SCONFITTO":"⚔ 2i RPG ⚔";
overlayText.textContent=isRetry
?"Premi RIPROVA per ricominciare la missione."
:"Scegli la tua classe e preparati all'avventura!";
}
selectClass("warrior");
showMenu(false);
resetGame();
requestAnimationFrame(update);
</script>
</body>
</html>
Game Source: 2i RPG - Fantasy Adventure
Creator: ShadowTurtle30
Libraries: none
Complexity: complex (388 lines, 14.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 2i-multiplayer-shadowturtle30" to link back to the original. Then publish at arcadelab.ai/publish.