<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>🚀 Rocket Factory</title>
<style>
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#050914;color:#fff;font-family:Tahoma,Arial,sans-serif}
body{touch-action:none}
#game{position:fixed;inset:0}
canvas{display:block}
.panel{
position:fixed;z-index:10;left:8px;right:8px;top:8px;
background:rgba(5,13,28,.84);border:1px solid #29466d;
border-radius:16px;padding:9px;backdrop-filter:blur(8px)
}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}
.stat{background:#0d1a31;border-radius:11px;padding:5px;text-align:center;font-size:10px}
.stat b{display:block;font-size:14px;margin-top:2px;color:#b9eaff}
#height{color:#70e7ff;font-size:18px}
#bottom{
position:fixed;z-index:11;bottom:8px;left:8px;right:8px;
display:flex;gap:6px;justify-content:center
}
button{
background:#172846;color:white;border:1px solid #31547f;border-radius:13px;
padding:11px 12px;font-weight:bold;font-size:13px
}
button:active{transform:scale(.96)}
button.primary{background:#087fbd;border-color:#35c7ff}
button.danger{background:#8d2634}
#screen{
position:fixed;inset:0;z-index:20;display:none;
background:rgba(2,7,17,.91);padding:65px 12px 70px;overflow:auto
}
#screen.show{display:block}
.box{
max-width:650px;margin:auto;background:#0a1529;border:1px solid #29466d;
border-radius:18px;padding:15px
}
h2{margin:4px 0 14px;font-size:20px}
h3{font-size:15px;margin:15px 0 8px}
.row{
display:flex;align-items:center;justify-content:space-between;
gap:8px;background:#101f37;border-radius:12px;padding:10px;margin:6px 0
}
.row small{color:#9bb0cc;display:block;margin-top:3px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:7px}
.card{background:#0f1e35;border-radius:13px;padding:10px}
.card strong{display:block;margin-bottom:5px}
.price{color:#ffd66e;font-size:12px}
.close{width:100%;margin-top:12px}
.bar{height:8px;background:#17243a;border-radius:20px;overflow:hidden;margin-top:6px}
.fill{height:100%;background:#31bce9;width:0%}
#message{
position:fixed;z-index:30;top:115px;left:50%;transform:translateX(-50%);
background:#0a162bde;border:1px solid #3c628d;padding:9px 13px;
border-radius:12px;display:none;white-space:nowrap
}
#message.show{display:block}
#flightControls{
position:fixed;z-index:12;bottom:75px;left:12px;right:12px;
display:none;justify-content:space-between;pointer-events:none
}
.ctrl{
pointer-events:auto;width:72px;height:72px;border-radius:50%;
background:#10243dd9;border:2px solid #47749f;font-size:24px
}
#launchHint{
position:fixed;z-index:4;left:50%;top:55%;transform:translate(-50%,-50%);
text-align:center;color:#b9cce2;pointer-events:none
}
@media(max-width:430px){
.stats{grid-template-columns:repeat(4,1fr)}
.stat b{font-size:12px}
button{font-size:11px;padding:10px 8px}
}
</style>
</head>
<body>
<div id="game"></div>
<div class="panel">
<div class="stats">
<div class="stat">پول<b id="money">10,000</b></div>
<div class="stat">سوخت<b id="fuel">0</b></div>
<div class="stat">سرعت<b id="speed">0 km/h</b></div>
<div class="stat">ارتفاع<b id="height">0 m</b></div>
</div>
<div style="margin-top:6px;font-size:10px;color:#9fb5d1">
رکورد: <span id="record">0 m</span> │ پروازها: <span id="flights">0</span>
</div>
</div>
<div id="launchHint">برای شروع، موشکت را آماده کن 🚀</div>
<div id="message"></div>
<div id="bottom">
<button onclick="openPanel('rocket')">🔧 موشک</button>
<button onclick="openPanel('factory')">🏭 کارخانه</button>
<button onclick="openPanel('shop')">🛒 فروشگاه</button>
<button class="primary" id="launchBtn" onclick="launch()">🚀 پرتاب</button>
</div>
<div id="flightControls">
<button class="ctrl" id="left">◀</button>
<button class="ctrl" id="right">▶</button>
</div>
<div id="screen">
<div class="box">
<h2 id="screenTitle">موشک</h2>
<div id="screenContent"></div>
<button class="close" onclick="closePanel()">بستن</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script>
/* =========================================================
ذخیره بازی
========================================================= */
const SAVE_KEY="rocket_factory_save_v1";
let data=JSON.parse(localStorage.getItem(SAVE_KEY)||"null")||{
money:10000,
fuel:0,
flights:0,
record:0,
iron:20,
steel:5,
aluminum:10,
titanium:0,
liquid:20,
rocket:{
body:1,
engine:1,
tank:1,
fins:1
}
};
function save(){
localStorage.setItem(SAVE_KEY,JSON.stringify(data));
updateHUD();
}
function money(n){
return Math.floor(n).toLocaleString("fa-IR");
}
function msg(t){
const e=document.getElementById("message");
e.textContent=t;
e.classList.add("show");
clearTimeout(msg.timer);
msg.timer=setTimeout(()=>e.classList.remove("show"),2200);
}
function updateHUD(){
document.getElementById("money").textContent=money(data.money);
document.getElementById("fuel").textContent=money(data.fuel);
document.getElementById("record").textContent=money(data.record)+" m";
document.getElementById("flights").textContent=money(data.flights);
document.getElementById("height").textContent=money(state.altitude)+" m";
document.getElementById("speed").textContent=money(Math.max(0,state.velocity*3.6))+" km/h";
}
/* =========================================================
THREE.JS
========================================================= */
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x071327);
scene.fog=new THREE.FogExp2(0x071327,.0018);
const camera=new THREE.PerspectiveCamera(
65,innerWidth/innerHeight,.1,50000
);
camera.position.set(8,7,14);
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.setSize(innerWidth,innerHeight);
renderer.shadowMap.enabled=true;
document.getElementById("game").appendChild(renderer.domElement);
const ambient=new THREE.HemisphereLight(0xb9dcff,0x14200d,1.8);
scene.add(ambient);
const sun=new THREE.DirectionalLight(0xffffff,2.2);
sun.position.set(30,60,20);
sun.castShadow=true;
scene.add(sun);
/* زمین */
const groundMat=new THREE.MeshStandardMaterial({color:0x27351e});
const ground=new THREE.Mesh(
new THREE.PlaneGeometry(10000,10000),
groundMat
);
ground.rotation.x=-Math.PI/2;
ground.receiveShadow=true;
scene.add(ground);
/* سکوی پرتاب */
const pad=new THREE.Mesh(
new THREE.CylinderGeometry(8,8,.5,32),
new THREE.MeshStandardMaterial({color:0x505967,metalness:.7,roughness:.35})
);
pad.position.y=.25;
pad.receiveShadow=true;
scene.add(pad);
const padRing=new THREE.Mesh(
new THREE.TorusGeometry(6,.18,10,48),
new THREE.MeshStandardMaterial({color:0x24b8e8,emissive:0x06384a})
);
padRing.rotation.x=Math.PI/2;
padRing.position.y=.53;
scene.add(padRing);
/* ابرها */
const clouds=[];
for(let i=0;i<45;i++){
const g=new THREE.Group();
const m=new THREE.MeshStandardMaterial({
color:0xdceaff,transparent:true,opacity:.7
});
for(let j=0;j<4;j++){
const c=new THREE.Mesh(new THREE.SphereGeometry(
18+Math.random()*18,10,8
),m);
c.position.set(j*20,Math.random()*8,Math.random()*15);
g.add(c);
}
g.position.set(
(Math.random()-.5)*1400,
300+Math.random()*1800,
(Math.random()-.5)*1400
);
g.scale.setScalar(.7+Math.random()*1.5);
scene.add(g);
clouds.push(g);
}
/* =========================================================
ساخت موشک
========================================================= */
let rocket=new THREE.Group();
scene.add(rocket);
function clearGroup(g){
while(g.children.length){
const o=g.children.pop();
if(o.geometry)o.geometry.dispose();
if(o.material)o.material.dispose();
}
}
function createRocket(){
clearGroup(rocket);
const bodyLevel=data.rocket.body;
const engineLevel=data.rocket.engine;
const tankLevel=data.rocket.tank;
const finLevel=data.rocket.fins;
const bodyRadius=.9+.08*bodyLevel;
const bodyLength=4.2+.35*bodyLevel;
const bodyMat=new THREE.MeshStandardMaterial({
color:0xcbd4df,
metalness:.7,
roughness:.27
});
const body=new THREE.Mesh(
new THREE.CylinderGeometry(bodyRadius,bodyRadius,bodyLength,32),
bodyMat
);
body.position.y=bodyLength/2+1;
body.castShadow=true;
rocket.add(body);
const nose=new THREE.Mesh(
new THREE.ConeGeometry(bodyRadius,1.8+.12*bodyLevel,32),
new THREE.MeshStandardMaterial({
color:0xeaf2fa,metalness:.6,roughness:.25
})
);
nose.position.y=bodyLength+1.9;
nose.castShadow=true;
rocket.add(nose);
const windowMat=new THREE.MeshStandardMaterial({
color:0x28bce9,metalness:.4,roughness:.15,
emissive:0x073e55
});
const window=new THREE.Mesh(
new THREE.SphereGeometry(.35+.03*bodyLevel,24,16),
windowMat
);
window.scale.z=.25;
window.position.set(0,bodyLength*.68+1,bodyRadius*.92);
rocket.add(window);
/* حلقهها */
for(let i=0;i<2+bodyLevel;i++){
const ring=new THREE.Mesh(
new THREE.TorusGeometry(bodyRadius+.02,.07,8,32),
new THREE.MeshStandardMaterial({color:0x596779,metalness:.8})
);
ring.rotation.x=Math.PI/2;
ring.position.y=1.7+i*(bodyLength/(3+bodyLevel));
rocket.add(ring);
}
/* بالهها */
const finMat=new THREE.MeshStandardMaterial({
color:0xc33c42,metalness:.5,roughness:.35
});
for(let i=0;i<3;i++){
const fin=new THREE.Mesh(
new THREE.BoxGeometry(.18,1.8+.2*finLevel,.75+.18*finLevel),
finMat
);
const a=i*Math.PI*2/3;
fin.position.set(
Math.cos(a)*(bodyRadius+.32),
1.65,
Math.sin(a)*(bodyRadius+.32)
);
fin.rotation.y=-a;
fin.castShadow=true;
rocket.add(fin);
}
/* موتور */
const nozzle=new THREE.Mesh(
new THREE.CylinderGeometry(.7+.07*engineLevel,.42,1.05+.12*engineLevel,24),
new THREE.MeshStandardMaterial({color:0x343b45,metalness:.9,roughness:.2})
);
nozzle.position.y=.65;
rocket.add(nozzle);
/* شعله */
const flame=new THREE.Mesh(
new THREE.ConeGeometry(.45+.06*engineLevel,2.2+.3*engineLevel,20),
new THREE.MeshBasicMaterial({
color:0xffa51f,transparent:true,opacity:.88
})
);
flame.position.y=-.9;
flame.name="flame";
flame.visible=false;
rocket.add(flame);
rocket.position.set(0,.6,0);
rocket.rotation.set(0,0,0);
}
createRocket();
/* =========================================================
حالت پرواز
========================================================= */
const state={
flying:false,
altitude:0,
velocity:0,
fuel:0,
time:0,
horizontal:0,
tilt:0,
crashed:false
};
function flightCapacity(){
return 18+data.rocket.tank*12;
}
function thrust(){
return 17+
data.rocket.engine*4+
data.rocket.body*0.7;
}
function efficiency(){
return .9+
data.rocket.engine*.04+
data.rocket.tank*.02;
}
function maxPotential(){
return Math.floor(
(flightCapacity()*thrust()*9)*
efficiency()*
(1+data.rocket.body*.045)
);
}
function launch(){
if(state.flying)return;
if(data.fuel<=0){
msg("⛽ سوخت نداری! از کارخانه سوخت بساز.");
openPanel("factory");
return;
}
state.flying=true;
state.crashed=false;
state.altitude=0;
state.velocity=0;
state.time=0;
state.horizontal=0;
state.tilt=0;
state.fuel=Math.min(data.fuel,flightCapacity());
data.fuel-=state.fuel;
document.getElementById("launchBtn").disabled=true;
document.getElementById("flightControls").style.display="flex";
document.getElementById("launchHint").style.display="none";
rocket.getObjectByName("flame").visible=true;
camera.fov=68;
camera.updateProjectionMatrix();
msg("🚀 پرتاب آغاز شد!");
}
function finishFlight(reason){
if(!state.flying)return;
state.flying=false;
rocket.getObjectByName("flame").visible=false;
document.getElementById("launchBtn").disabled=false;
document.getElementById("flightControls").style.display="none";
const earned=Math.max(100,Math.floor(
state.altitude*.65+
state.velocity*5+
data.rocket.body*30
));
data.money+=earned;
data.flights++;
if(state.altitude>data.record){
data.record=Math.floor(state.altitude);
msg("🏆 رکورد جدید! +" + money(earned)+" تومان");
}else{
msg("💰 درآمد پرواز: +"+money(earned)+" تومان");
}
if(reason==="crash"){
data.money=Math.max(0,data.money-50);
}
save();
}
/* =========================================================
پنلها
========================================================= */
function openPanel(type){
const screen=document.getElementById("screen");
const title=document.getElementById("screenTitle");
const content=document.getElementById("screenContent");
screen.classList.add("show");
if(type==="rocket"){
title.textContent="🔧 کارگاه موشک";
content.innerHTML=`
<div class="card">
<strong>موشک فعلی</strong>
ظرفیت سوخت: ${flightCapacity()} واحد<br>
توان موتور: ${thrust()}<br>
ارتفاع تخمینی: ${money(maxPotential())} متر
</div>
${upgradeHTML("بدنه","body",data.rocket.body,650,
"استحکام بیشتر و ارتفاع بهتر")}
${upgradeHTML("موتور","engine",data.rocket.engine,900,
"نیروی رانش بیشتر")}
${upgradeHTML("مخزن سوخت","tank",data.rocket.tank,750,
"ظرفیت سوخت بیشتر")}
${upgradeHTML("بالهها","fins",data.rocket.fins,500,
"پایداری بهتر هنگام پرواز")}
`;
}else if(type==="factory"){
title.textContent="🏭 کارخانه سوخت";
content.innerHTML=`
<div class="card">
مایع: <b>${money(data.liquid)}</b> واحد<br>
سوخت آماده: <b>${money(data.fuel)}</b> واحد
</div>
<div class="row">
<div>تبدیل 5 مایع به 4 سوخت<small>فرآیند پایه</small></div>
<button onclick="makeFuel(5,4)">تولید</button>
</div>
<div class="row">
<div>تبدیل 10 مایع + 2 آهن به 10 سوخت<small>سوخت تقویتشده</small></div>
<button onclick="makeFuelAdvanced()">تولید</button>
</div>
<div class="row">
<div>تبدیل 15 مایع + 2 فولاد به 17 سوخت<small>سوخت صنعتی</small></div>
<button onclick="makeFuelSteel()">تولید</button>
</div>
<h3>مواد اولیه</h3>
<div class="grid">
<div class="card">آهن<br><b>${money(data.iron)}</b></div>
<div class="card">فولاد<br><b>${money(data.steel)}</b></div>
<div class="card">آلومینیوم<br><b>${money(data.aluminum)}</b></div>
<div class="card">تیتانیوم<br><b>${money(data.titanium)}</b></div>
</div>
`;
}else if(type==="shop"){
title.textContent="🛒 فروشگاه مواد";
content.innerHTML=`
<div class="grid">
${shopCard("آهن","iron",1,35)}
${shopCard("فولاد","steel",1,90)}
${shopCard("آلومینیوم","aluminum",1,70)}
${shopCard("تیتانیوم","titanium",1,250)}
${shopCard("مایع سوخت","liquid",5,80)}
${shopCard("مایع ویژه","liquid",20,290)}
</div>
<h3>پول فعلی: ${money(data.money)} تومان</h3>
`;
}
}
function upgradeHTML(name,key,level,base,desc){
const price=Math.floor(base*Math.pow(1.55,level-1));
return `
<div class="row">
<div style="flex:1">
<b>${name} - سطح ${level}</b>
<small>${desc}</small>
</div>
<button onclick="upgrade('${key}',${price})">${money(price)} تومان</button>
</div>`;
}
function shopCard(name,key,amount,price){
return `
<div class="card">
<strong>${name}</strong>
+${amount} واحد
<div class="price">${money(price)} تومان</div>
<button style="margin-top:7px;width:100%" onclick="buy('${key}',${amount},${price})">خرید</button>
</div>`;
}
function closePanel(){
document.getElementById("screen").classList.remove("show");
}
function upgrade(key,price){
if(data.money<price){
msg("💸 پول کافی نیست.");
return;
}
data.money-=price;
data.rocket[key]++;
createRocket();
save();
openPanel("rocket");
msg("🔧 ارتقا انجام شد.");
}
function buy(key,amount,price){
if(data.money<price){
msg("💸 پول کافی نیست.");
return;
}
data.money-=price;
data[key]+=amount;
save();
openPanel("shop");
}
function makeFuel(liquidCost,out){
if(data.liquid<liquidCost){
msg("مایع کافی نیست.");
return;
}
data.liquid-=liquidCost;
data.fuel+=out;
save();
openPanel("factory");
msg("⛽ سوخت ساخته شد.");
}
function makeFuelAdvanced(){
if(data.liquid<10||data.iron<2){
msg("مواد لازم کافی نیست.");
return;
}
data.liquid-=10;
data.iron-=2;
data.fuel+=10;
save();
openPanel("factory");
msg("🔥 سوخت تقویتشده آماده شد.");
}
function makeFuelSteel(){
if(data.liquid<15||data.steel<2){
msg("مواد لازم کافی نیست.");
return;
}
data.liquid-=15;
data.steel-=2;
data.fuel+=17;
save();
openPanel("factory");
msg("🏭 سوخت صنعتی آماده شد.");
}
/* =========================================================
کنترل موبایل
========================================================= */
let steer=0;
function bindHold(id,val){
const e=document.getElementById(id);
const down=x=>{
x.preventDefault();
steer=val;
};
const up=x=>{
x.preventDefault();
if(steer===val)steer=0;
};
e.addEventListener("pointerdown",down);
e.addEventListener("pointerup",up);
e.addEventListener("pointercancel",up);
e.addEventListener("pointerleave",up);
}
bindHold("left",-1);
bindHold("right",1);
window.addEventListener("keydown",e=>{
if(e.key==="ArrowLeft")steer=-1;
if(e.key==="ArrowRight")steer=1;
});
window.addEventListener("keyup",e=>{
if(e.key==="ArrowLeft"&&steer===-1)steer=0;
if(e.key==="ArrowRight"&&steer===1)steer=0;
});
/* =========================================================
دوربین و فیزیک
========================================================= */
const clock=new THREE.Clock();
function updateFlight(dt){
if(!state.flying)return;
state.time+=dt;
const flame=rocket.getObjectByName("flame");
flame.visible=true;
flame.scale.y=.7+Math.random()*.55;
/* مصرف سوخت */
const consumption=(.65+data.rocket.engine*.08)*dt;
state.fuel-=consumption;
if(state.fuel<=0){
state.fuel=0;
rocket.getObjectByName("flame").visible=false;
state.velocity-=9*dt;
}
const gravity=9.81;
const accel=thrust()*.85;
state.velocity += (accel-gravity)*dt;
state.velocity=Math.max(state.velocity,-25);
state.altitude += state.velocity*dt;
/* کنترل افقی */
state.horizontal += steer*dt*(2.2+data.rocket.fins*.3);
state.horizontal*=.985;
state.tilt += steer*dt*.22;
state.tilt*=.94;
rocket.position.y=0.6+Math.max(0,state.altitude)/7;
rocket.position.x=state.horizontal;
rocket.rotation.z=state.tilt;
/* باد و لرزش */
rocket.rotation.x=Math.sin(state.time*5)*.008*(1+state.velocity/100);
if(state.altitude<0){
state.altitude=0;
finishFlight("crash");
return;
}
/* سوخت تمام شده و سرعت منفی شده */
if(state.fuel<=0&&state.velocity<0){
finishFlight("crash");
return;
}
updateHUD();
/* دوربین دنبال موشک */
const targetY=rocket.position.y+3.5;
const desired=new THREE.Vector3(
rocket.position.x+8,
targetY+5,
rocket.position.z+14
);
camera.position.lerp(desired,Math.min(1,dt*2.2));
const look=new THREE.Vector3(
rocket.position.x,
rocket.position.y+3,
rocket.position.z
);
camera.lookAt(look);
/* دوربین کمی بازتر میشود */
camera.fov=Math.min(82,68+state.altitude/180);
camera.updateProjectionMatrix();
/* ابرها */
for(const c of clouds){
c.rotation.y+=dt*.005;
}
}
/* =========================================================
دوربین روی زمین
========================================================= */
function groundCamera(dt){
if(state.flying)return;
const desired=new THREE.Vector3(9,6,15);
camera.position.lerp(desired,Math.min(1,dt*2));
camera.lookAt(0,3,0);
camera.fov=65;
camera.updateProjectionMatrix();
}
/* =========================================================
رندر
========================================================= */
function animate(){
requestAnimationFrame(animate);
const dt=Math.min(clock.getDelta(),.05);
updateFlight(dt);
groundCamera(dt);
renderer.render(scene,camera);
}
animate();
window.addEventListener("resize",()=>{
camera.aspect=innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
updateHUD();
</script>
</body>
</html>⚠️Content was pasted as plain text and auto-formatted as a code block. Use the Code Block button in the editor for proper formatting.