Country World
by SparkDragon183194 lines43.2 KB
<!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.