🎮ArcadeLab

Country World

by SparkDragon18
3194 lines43.2 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">

<title>Country World</title>

<style>
*{
    box-sizing:border-box;
    -webkit-tap-highlight-color:transparent;
}

html,body{
    margin:0;
    width:100%;
    height:100%;
    overflow:hidden;
    background:#05090e;
    color:#fff;
    font-family:Arial,sans-serif;
    user-select:none;
    touch-action:none;
}

body{
    overscroll-behavior:none;
}

button{
    border:1px solid #30445a;
    border-radius:10px;
    background:#142131;
    color:white;
    padding:10px 12px;
    font-weight:bold;
    font-size:12px;
}

button:active{
    transform:scale(.96);
}

#game{
    position:fixed;
    inset:0;
    overflow:hidden;
}


/* =========================
   ВЕРХНЯЯ ПАНЕЛЬ
========================= */

#top{
    position:absolute;
    z-index:50;

    top:0;
    left:0;
    right:0;

    height:62px;

    display:flex;
    gap:6px;
    align-items:center;

    padding:7px;

    background:#070d15ee;

    border-bottom:1px solid #26384b;
}

#countryName{
    flex:1;
    min-width:70px;

    overflow:hidden;
    white-space:nowrap;

    font-weight:bold;
    font-size:14px;
}

.stat{
    min-width:58px;

    padding:5px 7px;

    border:1px solid #26384b;
    border-radius:8px;

    background:#101a26;

    font-size:9px;
}

.stat b{
    display:block;
    font-size:13px;
    margin-top:2px;
}


/* =========================
   КАРТА
========================= */

#mapViewport{
    position:absolute;

    left:0;
    right:0;

    top:62px;
    bottom:68px;

    overflow:hidden;

    background:
        radial-gradient(
            circle at 50% 45%,
            #172b3d 0,
            #09151f 58%,
            #03070b 100%
        );

    touch-action:none;
}

#mapCamera{
    position:absolute;

    width:1800px;
    height:1100px;

    left:50%;
    top:50%;

    transform-origin:900px 550px;

    will-change:transform;

    pointer-events:none;
}

svg{
    display:block;

    width:1800px;
    height:1100px;

    overflow:visible;

    pointer-events:none;
}

.region{
    stroke:#263b50;
    stroke-width:5;

    pointer-events:auto;
}

.region.selected{
    stroke:#fff;
    stroke-width:7;

    filter:
        drop-shadow(0 0 5px #fff)
        drop-shadow(0 0 12px #fff);
}

.regionName{
    fill:#e6eef7;

    font-size:16px;
    font-weight:bold;

    pointer-events:none;
}

.frontShadow{
    fill:none;

    stroke:#ffffff55;
    stroke-width:14;

    stroke-linecap:round;
    stroke-linejoin:round;

    pointer-events:none;
}

.front{
    fill:none;

    stroke:white;
    stroke-width:6;

    stroke-linecap:round;
    stroke-linejoin:round;

    filter:
        drop-shadow(0 0 4px white)
        drop-shadow(0 0 9px white);

    pointer-events:none;
}


/* =========================
   ПЕРГАМЕНТНОЕ ОКНО
========================= */

#panel{
    position:absolute;

    z-index:60;

    right:12px;
    top:76px;

    width:300px;
    max-width:calc(100% - 24px);

    max-height:calc(100% - 90px);

    display:none;

    overflow:hidden;

    padding:0;

    border:0;

    border-radius:18px;

    background:transparent;

    box-shadow:
        0 14px 35px #000b;
}


/*
   ЭТО ИМЕННО ТА КАРТИНКА,
   КОТОРУЮ ТЫ ДАЛ.
*/

#paperCover{
    position:absolute;

    inset:0;

    width:100%;
    height:100%;

    object-fit:cover;

    object-position:center;

    pointer-events:none;

    z-index:0;
}


/*
   Лёгкое затемнение только для
   читаемости текста.
*/

#paperShade{
    position:absolute;

    inset:0;

    z-index:1;

    pointer-events:none;

    background:
        linear-gradient(
            rgba(255,238,195,.10),
            rgba(120,78,35,.08)
        );
}


/*
   Содержимое поверх картинки.
*/

#panelContent{
    position:relative;

    z-index:2;

    padding:30px 22px 22px;

    max-height:calc(100vh - 90px);

    overflow-y:auto;

    color:#392514;
}

#panel h2{
    margin:0 0 14px;

    text-align:center;

    font-family:Georgia,serif;

    font-size:24px;

    color:#3a2410;

    text-shadow:
        1px 1px 0 #f8e4b2,
        0 0 3px #fff3;
}

.info{
    padding:8px 3px;

    border-bottom:
        1px solid #65431f88;

    font-family:Georgia,serif;

    font-size:14px;

    color:#422912;
}

.info b{
    color:#211408;
}

.panelButtons{
    display:grid;

    grid-template-columns:1fr 1fr;

    gap:8px;

    margin-top:15px;
}

.panelButtons button{
    min-height:42px;

    border:2px solid #70491f;

    border-radius:9px;

    background:
        linear-gradient(
            #e8c982,
            #b98542
        );

    color:#34200d;

    font-family:Georgia,serif;

    font-size:13px;

    box-shadow:
        inset 0 1px 0 #fff8,
        0 2px 5px #4b2b1055;
}

.panelButtons button:active{
    background:
        linear-gradient(
            #c79b58,
            #98652e
        );
}

#infoButton{
    grid-column:1 / 3;

    font-size:15px;

    background:
        linear-gradient(
            #f0d798,
            #c1934d
        );
}

#closePanelButton{
    grid-column:1 / 3;
}


/* =========================
   АРМИЯ
========================= */

#armyPanel{
    position:absolute;

    z-index:60;

    left:10px;
    top:72px;

    width:250px;

    display:none;

    background:#08101af7;

    border:1px solid #40566d;

    border-radius:14px;

    padding:13px;

    box-shadow:
        0 15px 40px #000b;
}

#armyPanel h2{
    margin:0 0 10px;
    font-size:18px;
}

.armyRow{
    background:#101c29;

    border-radius:10px;

    padding:9px;

    margin:7px 0;
}

.armyRow button{
    width:100%;
    margin-top:6px;
}


/* =========================
   ВОЙНА
========================= */

#warOverlay{
    position:absolute;

    z-index:70;

    top:72px;
    left:50%;

    transform:translateX(-50%);

    width:min(
        450px,
        calc(100% - 20px)
    );

    padding:11px;

    background:#070e17f7;

    border:1px solid #52677d;

    border-radius:14px;

    display:none;

    text-align:center;
}

#warTitle{
    font-size:17px;
    font-weight:bold;
}

#warText{
    margin-top:4px;

    color:#b7c8d9;

    font-size:12px;
}

.bar{
    height:12px;

    margin-top:8px;

    border-radius:20px;

    overflow:hidden;

    background:#172535;
}

#warBar{
    height:100%;
    width:0;

    background:#368dff;
}


/* =========================
   НИЖНЕЕ МЕНЮ
========================= */

#bottom{
    position:absolute;

    z-index:50;

    left:0;
    right:0;
    bottom:0;

    height:68px;

    display:flex;
    gap:6px;

    padding:7px;

    background:#070d15ee;

    border-top:1px solid #26384b;
}

.action{
    flex:1;
    font-size:11px;
}


/* =========================
   УВЕДОМЛЕНИЕ
========================= */

#toast{
    position:absolute;

    z-index:100;

    left:50%;
    bottom:80px;

    transform:
        translate(-50%,20px);

    opacity:0;

    pointer-events:none;

    background:#101c29;

    border:1px solid #40566d;

    border-radius:11px;

    padding:10px 15px;

    transition:.18s;

    font-size:13px;
}

#toast.show{
    opacity:1;

    transform:
        translate(-50%,0);
}


/* =========================
   ПОДСКАЗКА
========================= */

#cameraHint{
    position:absolute;

    z-index:10;

    right:8px;
    bottom:7px;

    font-size:10px;

    color:#71869b;

    pointer-events:none;
}
</style>
</head>


<body>

<div id="game">


<!-- =========================
     ВЕРХ
========================= -->

<div id="top">

    <div id="countryName">
        Новая страна ★
    </div>

    <div class="stat">
        Деньги
        <b id="money">1000</b>
    </div>

    <div class="stat">
        Население
        <b id="population">12</b>
    </div>

    <div class="stat">
        Армия
        <b id="army">100</b>
    </div>

    <div class="stat">
        Ход
        <b id="turn">1</b>
    </div>

</div>


<!-- =========================
     КАРТА
========================= -->

<div id="mapViewport">

<div id="mapCamera">

<svg
viewBox="0 0 1800 1100"
xmlns="http://www.w3.org/2000/svg"
>

<defs id="defs"></defs>

<rect
width="1800"
height="1100"
fill="#081520"
/>


<g id="regions">

<path
id="north"
class="region"
d="M420 90 L600 35 L790 80 L850 220 L720 285 L510 250 L395 170 Z"
/>

<path
id="mountain"
class="region"
d="M790 80 L1000 30 L1190 95 L1160 235 L970 275 L850 220 Z"
/>

<path
id="northEast"
class="region"
d="M1190 95 L1380 145 L1440 275 L1320 345 L1160 235 Z"
/>

<path
id="west"
class="region"
d="M85 255 L340 205 L510 250 L485 430 L295 490 L105 445 Z"
/>

<path
id="central"
class="region"
d="M485 430 L510 250 L720 285 L850 220 L970 275 L900 455 L690 510 Z"
/>

<path
id="east"
class="region"
d="M970 275 L1160 235 L1320 345 L1260 500 L1060 535 L900 455 Z"
/>

<path
id="farEast"
class="region"
d="M1320 345 L1510 315 L1630 420 L1550 565 L1260 500 Z"
/>

<path
id="desert"
class="region"
d="M105 445 L295 490 L485 430 L690 510 L610 710 L340 775 L145 660 Z"
/>

<path
id="south"
class="region"
d="M690 510 L900 455 L1060 535 L1110 690 L850 775 L610 710 Z"
/>

<path
id="jose"
class="region"
d="M1510 315 L1660 300 L1740 400 L1630 510 L1550 565 L1630 420 Z"
/>

<path
id="coast"
class="region"
d="M1060 535 L1260 500 L1370 610 L1290 730 L1110 690 Z"
/>

<path
id="airport"
class="region"
d="M1370 610 L1570 575 L1690 680 L1600 790 L1290 730 Z"
/>

<path
id="industrial"
class="region"
d="M1040 730 L1290 730 L1400 835 L1210 900 L1000 820 Z"
/>

<path
id="eco"
class="region"
d="M850 775 L1000 820 L1080 970 L880 1030 L730 920 Z"
/>

<path
id="hub"
class="region"
d="M540 750 L730 710 L850 775 L730 920 L550 900 L465 820 Z"
/>

<path
id="islandWest"
class="region"
d="M160 790 L360 745 L465 820 L420 960 L210 980 L110 870 Z"
/>

<path
id="islandEast"
class="region"
d="M1370 850 L1580 810 L1720 900 L1630 1040 L1430 1010 Z"
/>

<path
id="southIsland"
class="region"
d="M540 930 L730 920 L880 1030 L760 1080 L570 1050 L450 990 Z"
/>

</g>


<g class="regionName">

<text x="550" y="150">
Север
</text>

<text x="900" y="150">
Горы
</text>

<text x="1260" y="205">
Северо-восток
</text>

<text x="220" y="350">
Запад
</text>

<text x="650" y="390">
Центр
</text>

<text x="1050" y="390">
Восток
</text>

<text x="1400" y="420">
Дальний восток
</text>

<text x="320" y="600">
Пустыня
</text>

<text x="790" y="630">
Юг
</text>

<text x="1570" y="390">
Джё Се
</text>

<text x="1130" y="625">
Побережье
</text>

<text x="1450" y="680">
Аэропорт
</text>

<text x="1120" y="810">
Промзона
</text>

<text x="860" y="920">
Эко-остров
</text>

<text x="590" y="820">
Хаб
</text>

<text x="220" y="875">
Западный остров
</text>

<text x="1450" y="925">
Восточный остров
</text>

<text x="590" y="1000">
Южный остров
</text>

</g>


<g id="battleLayer"></g>

</svg>

</div>

<div id="cameraHint">
1 палец — перемещение • 2 пальца — зум + вращение
</div>

</div>


<!-- =========================
     ОКНО С НАСТОЯЩЕЙ ОБЛОЖКОЙ
========================= -->

<div id="panel">

    <!-- КАРТИНКА ОБЛОЖКИ -->
    <img
        id="paperCover"
        src="https://png.pngtree.com/png-vector/20250529/ourmid/pngtree-aged-parchment-scroll-with-authentic-old-paper-texture-png-image_16403892.png"
        alt=""
    >

    <!-- ЛЁГКИЙ СЛОЙ ДЛЯ ЧИТАЕМОСТИ -->
    <div id="paperShade"></div>


    <!-- ВСЁ СОДЕРЖИМОЕ ПОВЕРХ КАРТИНКИ -->
    <div id="panelContent">

        <h2 id="regionTitle">
            Регион
        </h2>


        <div class="info">
            Владелец:
            <b id="regionOwner">—</b>
        </div>


        <div class="info">
            Население:
            <b id="regionPop">—</b>
            млн
        </div>


        <div class="info">
            Экономика:
            <b id="regionEco">—</b>
        </div>


        <div class="info">
            Ресурсы:
            <b id="regionRes">—</b>
        </div>


        <div class="info">
            Контроль:
            <b id="regionControl">—</b>
        </div>


        <div class="panelButtons">

            <button
                id="infoButton"
                onclick="showCountryInfo()"
            >
                INFO
            </button>


            <button onclick="startWar()">
                Война
            </button>


            <button onclick="developRegion()">
                Развитие
            </button>


            <button onclick="trade()">
                Торговля
            </button>


            <button
                id="closePanelButton"
                onclick="closePanel()"
            >
                Закрыть
            </button>

        </div>

    </div>

</div>


<!-- =========================
     АРМИЯ
========================= -->

<div id="armyPanel">

<h2>Армия</h2>

<div class="armyRow">

    Сила:
    <b id="armyPower">
        100
    </b>

    <button onclick="recruit()">
        Набрать +10
    </button>

</div>


<div class="armyRow">

    Уровень:
    <b id="armyLevel">
        1
    </b>

    <button onclick="trainArmy()">
        Тренировка
    </button>

</div>


<div class="armyRow">

    Оборона:
    <b id="defense">
        0
    </b>

    <button onclick="upgradeDefense()">
        Улучшить
    </button>

</div>


<button
style="width:100%"
onclick="closeArmy()"
>
Закрыть
</button>

</div>


<!-- =========================
     ВОЙНА
========================= -->

<div id="warOverlay">

<div id="warTitle">
    Военная операция
</div>

<div id="warText">
    Изменение границы
</div>

<div class="bar">

<div id="warBar"></div>

</div>

</div>


<!-- =========================
     НИЖНЕЕ МЕНЮ
========================= -->

<div id="bottom">

<button
class="action"
onclick="nextTurn()"
>
Ход
</button>

<button
class="action"
onclick="openArmy()"
>
Армия
</button>

<button
class="action"
onclick="randomEvent()"
>
Событие
</button>

<button
class="action"
onclick="openDiplomacy()"
>
Дипломатия
</button>

<button
class="action"
onclick="resetCamera()"
>
Центр
</button>

</div>


<div id="toast"></div>

</div>


<script>

/* =====================================================
   ОСНОВНЫЕ ДАННЫЕ
===================================================== */

const PLAYER_COLOR =
    "#2878d0";


const bots = [

    {
        name:"Норвелия",
        color:"#bd4545",
        army:105
    },

    {
        name:"Велория",
        color:"#8755b5",
        army:125
    },

    {
        name:"Сантерия",
        color:"#c58a35",
        army:90
    },

    {
        name:"Аркания",
        color:"#398e76",
        army:115
    }

];


const state = {

    country:"Новая страна",

    symbol:"★",

    money:1000,

    population:12,

    army:100,

    armyLevel:1,

    training:0,

    defense:0,

    turn:1

};


/* =====================================================
   РЕГИОНЫ
===================================================== */

const regions = {

north:{
    name:"Север",
    owner:"bot",
    bot:0,
    pop:7,
    eco:55,
    res:70
},

mountain:{
    name:"Горы",
    owner:"bot",
    bot:1,
    pop:6,
    eco:70,
    res:85
},

northEast:{
    name:"Северо-восток",
    owner:"bot",
    bot:2,
    pop:5,
    eco:75,
    res:60
},

west:{
    name:"Запад",
    owner:"bot",
    bot:2,
    pop:10,
    eco:65,
    res:50
},

central:{
    name:"Центр",
    owner:"player",
    bot:null,
    pop:12,
    eco:100,
    res:65
},

east:{
    name:"Восток",
    owner:"bot",
    bot:3,
    pop:9,
    eco:72,
    res:80
},

farEast:{
    name:"Дальний восток",
    owner:"bot",
    bot:0,
    pop:7,
    eco:60,
    res:75
},

desert:{
    name:"Пустыня",
    owner:"bot",
    bot:1,
    pop:4,
    eco:35,
    res:90
},

south:{
    name:"Юг",
    owner:"bot",
    bot:2,
    pop:8,
    eco:58,
    res:75
},

jose:{
    name:"Джё Се",
    owner:"bot",
    bot:2,
    pop:5,
    eco:80,
    res:65
},

coast:{
    name:"Побережье",
    owner:"bot",
    bot:3,
    pop:6,
    eco:85,
    res:55
},

airport:{
    name:"Аэропорт",
    owner:"bot",
    bot:3,
    pop:4,
    eco:95,
    res:45
},

industrial:{
    name:"Промзона",
    owner:"bot",
    bot:0,
    pop:8,
    eco:120,
    res:85
},

eco:{
    name:"Эко-остров",
    owner:"bot",
    bot:1,
    pop:3,
    eco:65,
    res:35
},

hub:{
    name:"Хаб",
    owner:"bot",
    bot:2,
    pop:3,
    eco:90,
    res:60
},

islandWest:{
    name:"Западный остров",
    owner:"bot",
    bot:3,
    pop:4,
    eco:55,
    res:40
},

islandEast:{
    name:"Восточный остров",
    owner:"bot",
    bot:0,
    pop:5,
    eco:75,
    res:65
},

southIsland:{
    name:"Южный остров",
    owner:"bot",
    bot:1,
    pop:4,
    eco:60,
    res:70
}

};


for(const id in regions){

    regions[id].control =
        regions[id].owner === "player"
        ? 1
        : 0;

}


/* =====================================================
   ФРОНТЫ
===================================================== */

const frontPoints = {

north:[
[420,120],[500,150],[590,125],[680,165],[780,135]
],

mountain:[
[800,120],[880,165],[960,125],[1050,170],[1140,145]
],

northEast:[
[1200,145],[1260,190],[1330,165],[1390,220],[1430,190]
],

west:[
[110,300],[190,350],[280,315],[370,370],[465,330]
],

central:[
[500,300],[570,360],[650,315],[740,380],[850,330]
],

east:[
[930,320],[990,385],[1060,345],[1140,410],[1220,370]
],

farEast:[
[1300,350],[1370,410],[1450,365],[1530,440],[1600,400]
],

desert:[
[130,500],[220,570],[320,525],[430,610],[570,550]
],

south:[
[650,520],[730,585],[820,530],[920,620],[1030,565]
],

jose:[
[1530,340],[1580,395],[1630,350],[1680,425],[1720,380]
],

coast:[
[1080,570],[1140,630],[1210,585],[1280,660],[1350,620]
],

airport:[
[1390,635],[1450,690],[1510,645],[1580,710],[1650,675]
],

industrial:[
[1060,750],[1130,805],[1210,755],[1290,830],[1370,795]
],

eco:[
[780,805],[850,860],[930,810],[1000,900],[1060,850]
],

hub:[
[500,780],[580,835],[660,790],[740,860],[820,810]
],

islandWest:[
[150,820],[220,875],[300,825],[370,900],[420,850]
],

islandEast:[
[1380,865],[1450,920],[1530,875],[1610,950],[1680,910]
],

southIsland:[
[480,955],[560,1005],[640,965],[720,1025],[800,990]
]

};


/* =====================================================
   CLIP PATHS
===================================================== */

const defs =
    document.getElementById("defs");

const battleLayer =
    document.getElementById("battleLayer");


for(const id in regions){

    const original =
        document.getElementById(id);

    const clip =
        document.createElementNS(
            "http://www.w3.org/2000/svg",
            "clipPath"
        );

    clip.id =
        "clip_" + id;


    const path =
        document.createElementNS(
            "http://www.w3.org/2000/svg",
            "path"
        );

    path.setAttribute(
        "d",
        original.getAttribute("d")
    );


    clip.appendChild(path);

    defs.appendChild(clip);
}


/* =====================================================
   ВЛАДЕЛЬЦЫ
===================================================== */

function getColor(id){

    const r =
        regions[id];


    if(r.owner === "player")
        return PLAYER_COLOR;


    return bots[r.bot]?.color ||
        "#566778";
}


function getOwner(id){

    const r =
        regions[id];


    if(r.owner === "player")
        return state.country;


    return bots[r.bot]?.name ||
        "Неизвестно";
}


/* =====================================================
   ФРОНТ
===================================================== */

function makeFrontPath(id){

    const r =
        regions[id];

    const points =
        frontPoints[id];


    if(!points)
        return "";


    const shift =
        (r.control - .5) * 280;


    let d = "";


    points.forEach(
        (p,i)=>{

            const wave =
                Math.sin(
                    i * 2.3 +
                    r.control * 9
                ) * 16;


            const x =
                p[0] +
                shift +
                wave;


            const y =
                p[1] +
                Math.sin(
                    i * 1.8 +
                    r.control * 5
                ) * 12;


            d +=
                (i === 0
                    ? "M "
                    : " L ") +
                x +
                " " +
                y;
        }
    );


    return d;
}


/* =====================================================
   ЦВЕТ ЗАХВАЧЕННОЙ ЧАСТИ
===================================================== */

function drawCapture(id){

    const r =
        regions[id];


    const old =
        document.getElementById(
            "capture_" + id
        );


    if(old)
        old.remove();


    if(
        r.control <= 0 ||
        r.control >= 1
    ){

        removeFront(id);

        return;
    }


    const points =
        frontPoints[id];


    const shift =
        (r.control - .5) * 280;


    let d = "";


    points.forEach(
        (p,i)=>{

            const wave =
                Math.sin(
                    i * 2.3 +
                    r.control * 9
                ) * 16;


            const x =
                p[0] +
                shift +
                wave;


            const y =
                p[1] +
                Math.sin(
                    i * 1.8 +
                    r.control * 5
                ) * 12;


            d +=
                (i === 0
                    ? "M "
                    : " L ") +
                x +
                " " +
                y;
        }
    );


    const last =
        points[
            points.length - 1
        ];


    const first =
        points[0];


    d +=
        ` L ${last[0] + shift + 150} 1200`;


    d +=
        ` L ${first[0] + shift - 150} 1200`;


    d += " Z";


    const group =
        document.createElementNS(
            "http://www.w3.org/2000/svg",
            "g"
        );


    group.id =
        "capture_" + id;


    group.setAttribute(
        "clip-path",
        `url(#clip_${id})`
    );


    const fill =
        document.createElementNS(
            "http://www.w3.org/2000/svg",
            "path"
        );


    fill.setAttribute(
        "d",
        d
    );


    fill.setAttribute(
        "fill",
        PLAYER_COLOR
    );


    group.appendChild(fill);

    battleLayer.appendChild(group);


    drawFront(id);
}


function drawFront(id){

    const r =
        regions[id];


    if(
        r.control <= 0 ||
        r.control >= 1
    ){

        removeFront(id);

        return;
    }


    const d =
        makeFrontPath(id);


    let shadow =
        document.getElementById(
            "frontShadow_" + id
        );


    if(!shadow){

        shadow =
            document.createElementNS(
                "http://www.w3.org/2000/svg",
                "path"
            );

        shadow.id =
            "frontShadow_" + id;

        shadow.classList.add(
            "frontShadow"
        );

        battleLayer.appendChild(
            shadow
        );
    }


    let front =
        document.getElementById(
            "front_" + id
        );


    if(!front){

        front =
            document.createElementNS(
                "http://www.w3.org/2000/svg",
                "path"
            );

        front.id =
            "front_" + id;

        front.classList.add(
            "front"
        );

        battleLayer.appendChild(
            front
        );
    }


    shadow.setAttribute(
        "d",
        d
    );


    front.setAttribute(
        "d",
        d
    );
}


function removeFront(id){

    [
        "front_" + id,
        "frontShadow_" + id,
        "capture_" + id
    ].forEach(
        key=>{

            const el =
                document.getElementById(
                    key
                );

            if(el)
                el.remove();
        }
    );
}


function renderMap(){

    for(const id in regions){

        const region =
            document.getElementById(id);


        region.setAttribute(
            "fill",
            getColor(id)
        );


        drawCapture(id);
    }
}


/* =====================================================
   ВЫБОР РЕГИОНА
===================================================== */

let selectedRegion =
    null;


function selectRegion(id){

    selectedRegion =
        id;


    document
    .querySelectorAll(".region")
    .forEach(
        r=>{
            r.classList.remove(
                "selected"
            );
        }
    );


    document
    .getElementById(id)
    .classList.add(
        "selected"
    );


    const r =
        regions[id];


    document.getElementById(
        "regionTitle"
    ).textContent =
        r.name;


    document.getElementById(
        "regionOwner"
    ).textContent =
        getOwner(id);


    document.getElementById(
        "regionPop"
    ).textContent =
        r.pop;


    document.getElementById(
        "regionEco"
    ).textContent =
        r.eco;


    document.getElementById(
        "regionRes"
    ).textContent =
        r.res;


    document.getElementById(
        "regionControl"
    ).textContent =
        Math.round(
            r.control * 100
        ) + "%";


    document.getElementById(
        "panel"
    ).style.display =
        "block";
}


function closePanel(){

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


    document
    .querySelectorAll(".region")
    .forEach(
        r=>{
            r.classList.remove(
                "selected"
            );
        }
    );


    selectedRegion =
        null;
}


/* =====================================================
   INFO
===================================================== */

function showCountryInfo(){

    if(!selectedRegion){

        toast(
            "Сначала выберите территорию"
        );

        return;
    }


    const r =
        regions[selectedRegion];


    const owner =
        getOwner(selectedRegion);


    const isPlayer =
        r.owner === "player";


    document.getElementById(
        "regionTitle"
    ).textContent =
        isPlayer
        ? state.country
        : owner;


    document.getElementById(
        "regionOwner"
    ).textContent =
        owner;


    document.getElementById(
        "regionPop"
    ).textContent =
        r.pop;


    document.getElementById(
        "regionEco"
    ).textContent =
        r.eco;


    document.getElementById(
        "regionRes"
    ).textContent =
        r.res;


    document.getElementById(
        "regionControl"
    ).textContent =
        Math.round(
            r.control * 100
        ) + "%";


    toast(
        isPlayer
        ? "Информация о вашей стране"
        : "Информация о " + owner
    );
}


/* =====================================================
   КАМЕРА
===================================================== */

const mapViewport =
    document.getElementById(
        "mapViewport"
    );


const mapCamera =
    document.getElementById(
        "mapCamera"
    );


const camera = {

    panX:0,

    panY:0,

    scale:.62,

    rotation:0

};


function updateCamera(){

    mapCamera.style.transform =
        `
        translate(-50%,-50%)
        translate(
            ${camera.panX}px,
            ${camera.panY}px
        )
        scale(${camera.scale})
        rotate(${camera.rotation}deg)
        `;
}


function resetCamera(){

    camera.panX = 0;

    camera.panY = 0;

    camera.scale = .62;

    camera.rotation = 0;

    updateCamera();
}


/* =====================================================
   УПРАВЛЕНИЕ КАРТОЙ
===================================================== */

const pointers =
    new Map();


let cameraMoved =
    false;


let tapCandidate =
    null;


let gesture = {

    distance:1,

    angle:0,

    centerX:0,

    centerY:0,

    panX:0,

    panY:0,

    scale:.62,

    rotation:0

};


function getPair(){

    const list =
        [...pointers.values()];


    if(list.length < 2)
        return null;


    return [
        list[0],
        list[1]
    ];
}


function getDistance(a,b){

    return Math.hypot(
        b.x-a.x,
        b.y-a.y
    );
}


function getAngle(a,b){

    return Math.atan2(
        b.y-a.y,
        b.x-a.x
    );
}


function getCenter(a,b){

    return {

        x:(a.x+b.x)/2,

        y:(a.y+b.y)/2

    };
}


mapViewport.addEventListener(
    "pointerdown",
    e=>{

        e.preventDefault();


        pointers.set(
            e.pointerId,
            {
                x:e.clientX,
                y:e.clientY,

                startX:e.clientX,
                startY:e.clientY
            }
        );


        try{

            mapViewport.setPointerCapture(
                e.pointerId
            );

        }catch(error){}


        if(pointers.size === 1){

            cameraMoved =
                false;


            const element =
                document.elementFromPoint(
                    e.clientX,
                    e.clientY
                );


            const region =
                element &&
                element.closest &&
                element.closest(
                    ".region"
                );


            tapCandidate =
                region
                ? region.id
                : null;
        }


        if(pointers.size === 2){

            cameraMoved =
                true;

            tapCandidate =
                null;


            const pair =
                getPair();


            const a =
                pair[0];

            const b =
                pair[1];


            const center =
                getCenter(a,b);


            gesture.distance =
                getDistance(a,b);


            gesture.angle =
                getAngle(a,b);


            gesture.centerX =
                center.x;


            gesture.centerY =
                center.y;


            gesture.panX =
                camera.panX;


            gesture.panY =
                camera.panY;


            gesture.scale =
                camera.scale;


            gesture.rotation =
                camera.rotation;
        }

    },
    {
        passive:false
    }
);


mapViewport.addEventListener(
    "pointermove",
    e=>{

        if(
            !pointers.has(
                e.pointerId
            )
        )
            return;


        e.preventDefault();


        const old =
            pointers.get(
                e.pointerId
            );


        const dx =
            e.clientX -
            old.x;


        const dy =
            e.clientY -
            old.y;


        const totalX =
            e.clientX -
            old.startX;


        const totalY =
            e.clientY -
            old.startY;


        pointers.set(
            e.pointerId,
            {
                x:e.clientX,
                y:e.clientY,

                startX:old.startX,
                startY:old.startY
            }
        );


        if(pointers.size === 1){

            if(
                Math.abs(totalX) > 7 ||
                Math.abs(totalY) > 7
            ){

                cameraMoved =
                    true;

                tapCandidate =
                    null;
            }


            if(cameraMoved){

                camera.panX += dx;

                camera.panY += dy;

                updateCamera();
            }


            return;
        }


        if(pointers.size === 2){

            const pair =
                getPair();


            const a =
                pair[0];

            const b =
                pair[1];


            const currentDistance =
                getDistance(a,b);


            const currentAngle =
                getAngle(a,b);


            const center =
                getCenter(a,b);


            camera.scale =
                gesture.scale *
                (
                    currentDistance /
                    gesture.distance
                );


            camera.scale =
                Math.max(
                    .25,
                    Math.min(
                        2.8,
                        camera.scale
                    )
                );


            let delta =
                currentAngle -
                gesture.angle;


            while(
                delta > Math.PI
            ){

                delta -=
                    Math.PI * 2;
            }


            while(
                delta < -Math.PI
            ){

                delta +=
                    Math.PI * 2;
            }


            camera.rotation =
                gesture.rotation +
                delta *
                180 /
                Math.PI;


            camera.panX =
                gesture.panX +
                center.x -
                gesture.centerX;


            camera.panY =
                gesture.panY +
                center.y -
                gesture.centerY;


            cameraMoved =
                true;


            updateCamera();
        }

    },
    {
        passive:false
    }
);


mapViewport.addEventListener(
    "pointerup",
    e=>{

        const wasLastPointer =
            pointers.size === 1;


        pointers.delete(
            e.pointerId
        );


        if(
            wasLastPointer &&
            tapCandidate &&
            !cameraMoved
        ){

            selectRegion(
                tapCandidate
            );
        }


        if(wasLastPointer){

            tapCandidate =
                null;

            cameraMoved =
                false;
        }


        if(pointers.size === 1){

            cameraMoved =
                true;
        }

    },
    {
        passive:false
    }
);


mapViewport.addEventListener(
    "pointercancel",
    e=>{

        pointers.delete(
            e.pointerId
        );

        tapCandidate =
            null;

        cameraMoved =
            false;

    }
);


/* =====================================================
   ВОЙНА
===================================================== */

let activeWar =
    null;


function startWar(){

    if(!selectedRegion){

        toast(
            "Сначала выберите территорию"
        );

        return;
    }


    if(activeWar){

        toast(
            "Война уже идёт"
        );

        return;
    }


    const r =
        regions[selectedRegion];


    if(r.owner === "player"){

        toast(
            "Это уже ваша территория"
        );

        return;
    }


    const enemy =
        bots[r.bot];


    if(!enemy)
        return;


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


    activeWar = {

        id:selectedRegion,

        enemy:enemy,

        timer:null

    };


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


    activeWar.timer =
        setInterval(
            warStep,
            500
        );


    warStep();
}


function warStep(){

    if(!activeWar)
        return;


    const id =
        activeWar.id;


    const r =
        regions[id];


    const enemy =
        activeWar.enemy;


    const playerPower =
        state.army +
        state.armyLevel * 15 +
        state.training * 5;


    let change =
        .035;


    if(
        playerPower >
        enemy.army
    ){

        change =
            .055;
    }


    if(
        playerPower >
        enemy.army * 1.3
    ){

        change =
            .075;
    }


    if(Math.random() < .15){

        r.control =
            Math.max(
                0,
                r.control -
                change * .7
            );

    }else{

        r.control =
            Math.min(
                1,
                r.control +
                change
            );
    }


    drawCapture(id);


    document.getElementById(
        "warBar"
    ).style.width =
        r.control * 100 + "%";


    document.getElementById(
        "warText"
    ).textContent =
        "Контроль территории: " +
        Math.round(
            r.control * 100
        ) +
        "%";


    if(r.control >= 1){

        r.control =
            1;

        r.owner =
            "player";

        r.bot =
            null;


        clearInterval(
            activeWar.timer
        );


        activeWar =
            null;


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


        state.population +=
            r.pop;


        state.money +=
            r.res * 2;


        renderMap();

        updateUI();


        toast(
            "Территория " +
            r.name +
            " перешла под ваш контроль"
        );


        return;
    }


    updateUI();
}


/* =====================================================
   АРМИЯ
===================================================== */

function recruit(){

    if(state.money < 100){

        toast(
            "Недостаточно денег"
        );

        return;
    }


    state.money -=
        100;

    state.army +=
        10;


    updateUI();


    toast(
        "Армия +10"
    );
}


function trainArmy(){

    if(state.money < 150){

        toast(
            "Недостаточно денег"
        );

        return;
    }


    state.money -=
        150;

    state.training++;


    if(
        state.training % 2 === 0 &&
        state.armyLevel < 10
    ){

        state.armyLevel++;
    }


    updateUI();


    toast(
        "Армия усилена"
    );
}


function upgradeDefense(){

    if(state.money < 200){

        toast(
            "Недостаточно денег"
        );

        return;
    }


    state.money -=
        200;

    state.defense++;


    updateUI();


    toast(
        "Оборона улучшена"
    );
}


/* =====================================================
   РАЗВИТИЕ
===================================================== */

function developRegion(){

    if(!selectedRegion){

        toast(
            "Сначала выберите территорию"
        );

        return;
    }


    const r =
        regions[selectedRegion];


    if(r.owner !== "player"){

        toast(
            "Развивать можно только свою территорию"
        );

        return;
    }


    if(state.money < 150){

        toast(
            "Недостаточно денег"
        );

        return;
    }


    state.money -=
        150;

    r.eco +=
        10;


    updateUI();


    selectRegion(
        selectedRegion
    );


    toast(
        "Экономика региона улучшена"
    );
}


/* =====================================================
   ТОРГОВЛЯ
===================================================== */

function trade(){

    if(!selectedRegion){

        toast(
            "Сначала выберите территорию"
        );

        return;
    }


    const r =
        regions[selectedRegion];


    if(r.owner === "player"){

        state.money +=
            90;


        updateUI();


        toast(
            "Торговля принесла +90"
        );

    }else{

        toast(
            "Торговля с этой территорией пока недоступна"
        );
    }
}


/* =====================================================
   ХОД
===================================================== */

function nextTurn(){

    state.turn++;


    let income =
        0;


    for(const id in regions){

        const r =
            regions[id];


        if(r.owner === "player"){

            income +=
                Math.floor(
                    r.eco / 10
                );
        }
    }


    state.money +=
        income;


    updateUI();


    toast(
        "Ход " +
        state.turn +
        " • доход +" +
        income
    );
}


/* =====================================================
   СОБЫТИЯ
===================================================== */

function randomEvent(){

    const events = [

        ["Найден ресурс",150],

        ["Торговый договор",120],

        ["Экономический рост",200],

        ["Государственные расходы",-100]

    ];


    const event =
        events[
            Math.floor(
                Math.random() *
                events.length
            )
        ];


    state.money +=
        event[1];


    state.money =
        Math.max(
            0,
            state.money
        );


    updateUI();


    toast(
        event[0] +
        " " +
        (
            event[1] >= 0
            ? "+"
            : ""
        ) +
        event[1]
    );
}


/* =====================================================
   ДИПЛОМАТИЯ
===================================================== */

function openDiplomacy(){

    toast(
        "Дипломатия пока в разработке"
    );
}


/* =====================================================
   UI
===================================================== */

function updateUI(){

    document.getElementById(
        "countryName"
    ).textContent =
        state.country +
        " " +
        state.symbol;


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


    document.getElementById(
        "population"
    ).textContent =
        state.population;


    document.getElementById(
        "army"
    ).textContent =
        state.army;


    document.getElementById(
        "turn"
    ).textContent =
        state.turn;


    document.getElementById(
        "armyPower"
    ).textContent =
        state.army;


    document.getElementById(
        "armyLevel"
    ).textContent =
        state.armyLevel;


    document.getElementById(
        "defense"
    ).textContent =
        state.defense;
}


function openArmy(){

    document.getElementById(
        "armyPanel"
    ).style.display =
        "block";
}


function closeArmy(){

    document.getElementById(
        "armyPanel"
    ).style.display =
        "none";
}


/* =====================================================
   TOAST
===================================================== */

let toastTimer =
    null;


function toast(text){

    const el =
        document.getElementById(
            "toast"
        );


    el.textContent =
        text;


    el.classList.add(
        "show"
    );


    clearTimeout(
        toastTimer
    );


    toastTimer =
        setTimeout(
            ()=>{
                el.classList.remove(
                    "show"
                );
            },
            2200
        );
}


/* =====================================================
   ЗАПУСК
===================================================== */

renderMap();

updateUI();

resetCamera();

</script>

</body>
</html>

Game Source: Country World

Creator: SparkDragon18

Libraries: none

Complexity: complex (3194 lines, 43.2 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: country-world-sparkdragon18" to link back to the original. Then publish at arcadelab.ai/publish.