🎮ArcadeLab

شهر آزاد

by LuckyLion22
1321 lines17.3 KB
▶ Play
بزن بریم 😎🎮 این نسخه یک‌تکه است و منوی شروع، انتخاب شخصیت، شب‌وروز و ذخیره/بارگذاری بازی دارد.
Writing
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>شهر آزاد</title>

<style>
*{box-sizing:border-box}

html,body{
    margin:0;
    width:100%;
    height:100%;
    overflow:hidden;
    font-family:Arial;
    background:#111;
}

canvas{
    display:block;
}

#menu{
    position:fixed;
    inset:0;
    z-index:20;
    display:flex;
    flex-direction:column;
    justify-content:center;
    align-items:center;
    background:linear-gradient(#111c,#000e);
    color:white;
}

#menu h1{
    font-size:42px;
    margin:5px;
}

#menu p{
    color:#ddd;
}

button{
    border:0;
    border-radius:12px;
    padding:12px 24px;
    margin:6px;
    font-size:17px;
    cursor:pointer;
}

.characters{
    display:flex;
    gap:15px;
}

.character{
    width:80px;
    height:90px;
    border:2px solid #777;
    border-radius:12px;
    display:flex;
    justify-content:center;
    align-items:center;
    font-size:35px;
    cursor:pointer;
}

.character.selected{
    border-color:#ffe000;
    background:#ffe00022;
}

#hud{
    position:fixed;
    top:10px;
    right:10px;
    z-index:10;
    background:#000c;
    color:white;
    padding:12px 15px;
    border-radius:14px;
    line-height:1.8;
}

#save{
    position:fixed;
    left:10px;
    top:10px;
    z-index:10;
    display:none;
}
</style>
</head>

<body>

<div id="menu">

    <h1>🏙️ شهر آزاد</h1>

    <p>شخصیتت را انتخاب کن</p>

    <div class="characters">

        <div class="character selected"
             data-color="#1976d2">
            🧑
        </div>

        <div class="character"
             data-color="#e91e63">
            👩
        </div>

        <div class="character"
             data-color="#4caf50">
            🧑‍🦱
        </div>

    </div>

    <br>

    <button onclick="startGame()">
        ▶ شروع بازی
    </button>

    <button onclick="loadGame()">
        💾 بارگذاری ذخیره
    </button>

</div>


<div id="hud">

❤️ جان:
<span id="health">100</span>

<br>

💰 پول:
<span id="money">500</span>

<br>

⭐ تحت تعقیب:
<span id="wanted">0</span>

<br>

🕐 زمان:
<span id="time">روز</span>

<br>

🎯
<span id="mission">
به علامت زرد برو
</span>

<br>

🚗 E = ماشین

</div>


<button id="save"
        onclick="saveGame()">
💾 ذخیره
</button>


<canvas id="game"></canvas>


<script>

// =======================================
// CANVAS
// =======================================

const canvas =
document.getElementById("game");

const ctx =
canvas.getContext("2d");

function resize(){

    canvas.width=innerWidth;
    canvas.height=innerHeight;

}

resize();

addEventListener("resize",resize);


// =======================================
// MENU
// =======================================

let selectedColor="#1976d2";

document
.querySelectorAll(".character")
.forEach(character=>{

    character.onclick=()=>{

        document
        .querySelectorAll(".character")
        .forEach(c=>c.classList.remove("selected"));

        character.classList.add("selected");

        selectedColor=
        character.dataset.color;

    };

});


// =======================================
// WORLD
// =======================================

const world={
    width:3600,
    height:2400
};


// =======================================
// PLAYER
// =======================================

let player={
    x:650,
    y:650,
    radius:16,
    speed:4,
    health:100,
    color:"#1976d2"
};


// =======================================
// MONEY
// =======================================

let money=500;


// =======================================
// WANTED
// =======================================

let wanted=0;


// =======================================
// TIME
// =======================================

let gameTime=8;


// =======================================
// CAR
// =======================================

let cars=[
    {
        x:800,
        y:600,
        color:"#e53935",
        speed:8
    },
    {
        x:1200,
        y:500,
        color:"#2196f3",
        speed:9
    },
    {
        x:1700,
        y:1000,
        color:"#f1c40f",
        speed:10
    }
];

let currentCar=null;


// =======================================
// POLICE
// =======================================

let police=[
    {x:300,y:300},
    {x:3200,y:300},
    {x:300,y:2100}
];


// =======================================
// NPC
// =======================================

let npcs=[];

for(let i=0;i<20;i++){

    npcs.push({

        x:200+Math.random()*3100,

        y:250+Math.random()*1900,

        dx:(Math.random()-.5),

        dy:(Math.random()-.5)

    });

}


// =======================================
// MISSIONS
// =======================================

let missions=[

    {x:1000,y:500},

    {x:1900,y:800},

    {x:2800,y:1700},

    {x:600,y:1900}

];

let missionIndex=0;


// =======================================
// BUILDINGS
// =======================================

const buildings=[];

for(let row=0;row<3;row++){

    for(let col=0;col<5;col++){

        buildings.push({

            x:80+col*700,

            y:80+row*700,

            w:430,

            h:330

        });

    }

}


// =======================================
// KEYS
// =======================================

let keys={};

addEventListener("keydown",e=>{

    keys[e.key.toLowerCase()]=true;

    if(e.key.toLowerCase()==="e"){
        enterCar();
    }

});

addEventListener("keyup",e=>{

    keys[e.key.toLowerCase()]=false;

});


// =======================================
// COLLISION
// =======================================

function collision(x,y){

    for(let b of buildings){

        if(

            x>b.x-20 &&

            x<b.x+b.w+20 &&

            y>b.y-20 &&

            y<b.y+b.h+20

        ){

            return true;

        }

    }

    return false;

}


// =======================================
// START
// =======================================

function startGame(){

    player.color=selectedColor;

    document
    .getElementById("menu")
    .style.display="none";

    document
    .getElementById("save")
    .style.display="block";

}


// =======================================
// CAR
// =======================================

function enterCar(){

    if(currentCar){

        currentCar=null;

        return;

    }

    let nearest=null;

    let best=85;

    for(let car of cars){

        let d=Math.hypot(

            player.x-car.x,

            player.y-car.y

        );

        if(d<best){

            best=d;

            nearest=car;

        }

    }

    if(nearest){

        currentCar=nearest;

        wanted=Math.min(5,wanted+1);

    }

}


// =======================================
// PLAYER UPDATE
// =======================================

function updatePlayer(){

    let dx=0;
    let dy=0;

    if(keys["w"])dy--;
    if(keys["s"])dy++;
    if(keys["a"])dx--;
    if(keys["d"])dx++;

    if(!dx&&!dy)return;

    let length=Math.hypot(dx,dy);

    dx/=length;
    dy/=length;

    let speed=

        currentCar

        ?currentCar.speed

        :player.speed;

    let nx=player.x+dx*speed;
    let ny=player.y+dy*speed;

    if(!collision(nx,player.y))
        player.x=nx;

    if(!collision(player.x,ny))
        player.y=ny;


    player.x=Math.max(

        20,

        Math.min(

            world.width-20,

            player.x

        )

    );

    player.y=Math.max(

        20,

        Math.min(

            world.height-20,

            player.y

        )

    );


    if(currentCar){

        currentCar.x=player.x;
        currentCar.y=player.y;

    }

}


// =======================================
// NPC
// =======================================

function updateNPC(){

    for(let n of npcs){

        n.x+=n.dx;
        n.y+=n.dy;

        if(
            n.x<20 ||
            n.x>world.width-20
        )
            n.dx*=-1;

        if(
            n.y<20 ||
            n.y>world.height-20
        )
            n.dy*=-1;

    }

}


// =======================================
// POLICE
// =======================================

function updatePolice(){

    if(wanted<=0)return;

    for(let p of police){

        let dx=player.x-p.x;
        let dy=player.y-p.y;

        let d=Math.hypot(dx,dy);

        if(d<900 && d>1){

            p.x+=dx/d*2;
            p.y+=dy/d*2;

        }

        if(d<35){

            player.health-=0.08;

        }

    }

}


// =======================================
// MISSION
// =======================================

function updateMission(){

    let m=missions[missionIndex];

    if(!m)return;

    let d=Math.hypot(

        player.x-m.x,

        player.y-m.y

    );

    if(d<65){

        money+=500;

        wanted=0;

        missionIndex++;

        if(
            missionIndex>=missions.length
        ){

            document
            .getElementById("mission")
            .textContent=
            "🏆 همه مأموریت‌ها تمام شد!";

        }else{

            document
            .getElementById("mission")
            .textContent=
            "مأموریت بعدی را پیدا کن";

        }

    }

}


// =======================================
// DAY / NIGHT
// =======================================

function updateTime(){

    gameTime+=0.002;

    if(gameTime>=24)
        gameTime=0;

}


// =======================================
// CITY
// =======================================

function drawCity(){

    ctx.fillStyle="#65a653";

    ctx.fillRect(

        0,
        0,
        world.width,
        world.height

    );


    ctx.fillStyle="#444";

    for(
        let y=400;
        y<world.height;
        y+=700
    ){

        ctx.fillRect(

            0,
            y,
            world.width,
            150

        );

    }


    for(
        let x=500;
        x<world.width;
        x+=700
    ){

        ctx.fillRect(

            x,
            0,
            150,
            world.height

        );

    }


    for(let b of buildings){

        ctx.fillStyle="#777";

        ctx.fillRect(

            b.x,
            b.y,
            b.w,
            b.h

        );

        ctx.fillStyle="#a9d8e5";

        for(
            let x=b.x+30;
            x<b.x+b.w-20;
            x+=65
        ){

            for(
                let y=b.y+30;
                y<b.y+b.h-20;
                y+=65
            ){

                ctx.fillRect(
                    x,
                    y,
                    25,
                    25
                );

            }

        }

    }

}


// =======================================
// CARS
// =======================================

function drawCars(){

    for(let car of cars){

        ctx.save();

        ctx.translate(
            car.x,
            car.y
        );

        ctx.fillStyle=car.color;

        ctx.fillRect(
            -35,
            -18,
            70,
            36
        );

        ctx.fillStyle="#222";

        ctx.fillRect(
            -20,
            -13,
            18,
            14
        );

        ctx.fillRect(
            5,
            -13,
            18,
            14
        );

        ctx.restore();

    }

}


// =======================================
// NPC
// =======================================

function drawNPC(){

    for(let n of npcs){

        ctx.fillStyle="#ffb74d";

        ctx.beginPath();

        ctx.arc(
            n.x,
            n.y,
            10,
            0,
            Math.PI*2
        );

        ctx.fill();

    }

}


// =======================================
// POLICE
// =======================================

function drawPolice(){

    for(let p of police){

        ctx.save();

        ctx.translate(
            p.x,
            p.y
        );

        ctx.fillStyle="#eee";

        ctx.fillRect(
            -30,
            -17,
            60,
            34
        );

        ctx.fillStyle="red";

        ctx.fillRect(
            -18,
            -23,
            16,
            6
        );

        ctx.fillStyle="blue";

        ctx.fillRect(
            2,
            -23,
            16,
            6
        );

        ctx.restore();

    }

}


// =======================================
// PLAYER
// =======================================

function drawPlayer(){

    if(currentCar)return;

    ctx.fillStyle=player.color;

    ctx.beginPath();

    ctx.arc(

        player.x,
        player.y,
        player.radius,
        0,
        Math.PI*2

    );

    ctx.fill();

}


// =======================================
// MISSION
// =======================================

function drawMission(){

    let m=missions[missionIndex];

    if(!m)return;

    ctx.strokeStyle="#ffe000";

    ctx.lineWidth=7;

    ctx.beginPath();

    ctx.arc(

        m.x,
        m.y,
        45,
        0,
        Math.PI*2

    );

    ctx.stroke();

}


// =======================================
// MINIMAP
// =======================================

function drawMiniMap(){

    let size=150;

    let x=
        canvas.width-size-15;

    let y=
        canvas.height-size-15;

    ctx.fillStyle="#111d";

    ctx.fillRect(
        x,
        y,
        size,
        size
    );

    ctx.strokeStyle="#fff";

    ctx.strokeRect(
        x,
        y,
        size,
        size
    );


    ctx.fillStyle="#00e5ff";

    ctx.beginPath();

    ctx.arc(

        x+(player.x/world.width)*size,

        y+(player.y/world.height)*size,

        4,

        0,
        Math.PI*2

    );

    ctx.fill();


    ctx.fillStyle="red";

    for(let p of police){

        ctx.fillRect(

            x+(p.x/world.width)*size-2,

            y+(p.y/world.height)*size-2,

            5,
            5

        );

    }


    let m=missions[missionIndex];

    if(m){

        ctx.fillStyle="yellow";

        ctx.fillRect(

            x+(m.x/world.width)*size-3,

            y+(m.y/world.height)*size-3,

            6,
            6

        );

    }

}


// =======================================
// NIGHT
// =======================================

function drawNight(){

    let darkness=0;

    if(gameTime>=19 || gameTime<6){

        darkness=0.45;

    }else if(
        gameTime>=17 ||
        gameTime<8
    ){

        darkness=0.2;

    }

    if(darkness){

        ctx.fillStyle=
            `rgba(10,20,60,${darkness})`;

        ctx.fillRect(

            0,
            0,
            canvas.width,
            canvas.height

        );

    }

}


// =======================================
// DRAW
// =======================================

function draw(){

    let camX=
        player.x-canvas.width/2;

    let camY=
        player.y-canvas.height/2;

    camX=Math.max(

        0,

        Math.min(

            world.width-canvas.width,

            camX

        )

    );

    camY=Math.max(

        0,

        Math.min(

            world.height-canvas.height,

            camY

        )

    );


    ctx.clearRect(

        0,
        0,
        canvas.width,
        canvas.height

    );


    ctx.save();

    ctx.translate(
        -camX,
        -camY
    );

    drawCity();
    drawMission();
    drawCars();
    drawNPC();
    drawPolice();
    drawPlayer();

    ctx.restore();


    drawNight();
    drawMiniMap();


    document
    .getElementById("health")
    .textContent=
        Math.max(
            0,
            Math.floor(player.health)
        );


    document
    .getElementById("money")
    .textContent=money;


    document
    .getElementById("wanted")
    .textContent=
        "⭐".repeat(wanted);


    let hour=Math.floor(gameTime);

    document
    .getElementById("time")
    .textContent=
        hour<6 || hour>=19
        ?"شب 🌙"
        :"روز ☀️";

}


// =======================================
// SAVE
// =======================================

function saveGame(){

    const save={

        player,
        money,
        wanted,
        gameTime,
        missionIndex,
        cars

    };

    localStorage.setItem(

        "shahrAzadSave",

        JSON.stringify(save)

    );

    alert("💾 بازی ذخیره شد!");

}


// =======================================
// LOAD
// =======================================

function loadGame(){

    let data=
        localStorage.getItem(
            "shahrAzadSave"
        );

    if(!data){

        alert("ذخیره‌ای پیدا نشد!");

        return;

    }

    let save=
        JSON.parse(data);

    player=save.player;
    money=save.money;
    wanted=save.wanted;
    gameTime=save.gameTime;
    missionIndex=save.missionIndex;
    cars=save.cars;

    document
    .getElementById("menu")
    .style.display="none";

    document
    .getElementById("save")
    .style.display="block";

}


// =======================================
// GAME LOOP
// =======================================

function gameLoop(){

    updatePlayer();
    updateNPC();
    updatePolice();
    updateMission();
    updateTime();

    draw();

    requestAnimationFrame(gameLoop);

}

gameLoop();

</script>

</body>
</html>
کنترل: WASD حرکت، E سوار/پیاده شدن، دایرهٔ زرد مأموریت است. 💾 دکمهٔ ذخیره هم پیشرفتت را در مرورگر نگه می‌دارد.

Game Source: شهر آزاد

Creator: LuckyLion22

Libraries: none

Complexity: complex (1321 lines, 17.3 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: game-luckylion22" to link back to the original. Then publish at arcadelab.ai/publish.