// 국가 외교·전쟁 시뮬레이션 — GDevelop "JavaScript 코드 실행" 액션용 (단일 스크립트) // 사용법: 이벤트 시트 → 조건 "씬 시작 시" → 액션 "Execute a JavaScript code" 에 이 파일 전체를 붙여넣기 (function(){ if(window.NS)return; const rnd=Math.random,n=x=>Math.round(x),cl=(x,a,b)=>Math.max(a,Math.min(b,x)); const RN=['🌾밀','⛏️철','🐟수산','🛢️석유','🪵목재','🍎과일'],RP=[4,8,5,12,3,6],RC=['#e8c547','#c5ced8','#5ad0e0','#222','#7a4a24','#ff5d6c']; const NM=['아르카','벨로','카엘','도른','에스타','펠라','그레이','히로'],CL=['#e5484d','#3e8bff','#d98e1c','#a96bff','#2fbf71','#e0649c','#27a3a3','#9aab2a']; const TY=['공격적','평화적','무역중심','자원중심','군사중심','외교중심','고립주의']; // [전쟁, 군비, 동맹, 선물, 무역협정, 판매] const PR=[[.07,.4,.03,0,.02,.05],[0,.1,.05,.06,.04,.05],[.005,.2,.04,.04,.12,.15],[.01,.2,.03,.02,.06,.3],[.05,.6,.03,0,.02,.05],[.005,.2,.09,.12,.06,.08],[.01,.2,0,0,0,.02]]; const MK={trade:'교역',help:'요청 승인',refuse:'요청 거절',gift:'선물',firststrike:'선제공격',brk:'조약 위반',aid:'전후 지원',nuke:'핵 사용',pact:'무역협정'}; const GW=40,GH=26,NC=8,CS=11,SK='nationsim-v1'; let own,reg,RG,coast,C,rel,mem,wars,pend,logs,chat,hist,pacts,turn,aid,H,me,tab='e',spd=0,acc=0,mute=false,showChat=false,cen,fx=[],frame=0,sp=[]; const sel={r:0,q:25,d:150,g:-1}; const wf=(a,b)=>wars.find(w=>(w.a==a&&w.b==b)||(w.a==b&&w.b==a)); const L=(t,m)=>{logs.unshift(`[${t}] T${turn} ${m}`);if(logs.length>80)logs.pop()}; const CH=m=>{chat.unshift(`T${turn} ${m}`);if(chat.length>80)chat.pop()}; const rem=(a,b,k,v)=>{mem[a][b].push({T:turn,k,v});if(mem[a][b].length>14)mem[a][b].shift();rel[a][b]=cl(rel[a][b]+v,-100,100)}; // ---------- 소리 ---------- let AC;function snd(k){if(mute)return;try{AC=AC||new(window.AudioContext||window.webkitAudioContext)(); const f={ok:[660,880],no:[300,200],war:[180,120,90],nuke:[60,40,30,25],click:[500],turn:[440,550]}[k]; f.forEach((fr,i)=>{const o=AC.createOscillator(),g=AC.createGain();o.type=(k=='nuke'||k=='war')?'sawtooth':'square';o.frequency.value=fr;g.gain.value=.05; o.connect(g);g.connect(AC.destination);const t=AC.currentTime+i*.09;o.start(t);g.gain.exponentialRampToValueAtTime(.001,t+.14);o.stop(t+.15)})}catch(e){}} // ---------- UI 뼈대 ---------- const root=document.createElement('div');root.id='ns'; root.innerHTML=`
지도를 탭하면 지역 정보가 보여요
`; document.body.appendChild(root); const $=i=>root.querySelector('#'+i),cv=$('nc'),ctx=cv.getContext('2d'); // ---------- 세계 생성 ---------- function mkCoast(){coast=own.map((o,i)=>{if(o<0)return false;const x=i%GW,y=i/GW|0;return(x>0&&own[i-1]<0)||(x0&&own[i-GW]<0)||(y>2)*13+6.5;[[-3,-3],[3,-2],[0,4]].forEach(o=>sd.push({x:cx+o[0]+rnd()*2-1,y:cy+o[1]+rnd()*2-1,o:i}))} const a=rnd()*6,b=rnd()*6;own=[];reg=[]; for(let y=0;y=GW-1||y>=GH-1||(Math.abs(y-12.5+Math.sin(x*.45+a)*1.4)<.8&&Math.sin(x*.9+b)>-.3)||Math.sin(x*.5+a)*Math.sin(y*.6+b)>.85; if(sea){own.push(-1);reg.push(-1);continue} let bi=0,bd=1e9;sd.forEach((s,i)=>{const d=Math.hypot(s.x-x,s.y-y);if(d({id:i,o:s.o,res:0,dmg:0,n:NM[s.o]+' '+['북','동','남'][i%3]+'부'}));mkCoast()} function newGame(){gen(); C=NM.map((nm,i)=>({i,n:nm,c:CL[i],st:Array(6).fill(40),D:i?800:1000,tp:50,mil:60,df:0,sh:2,bm:1,ms:2,al:-1,ty:i?rnd()*7|0:-1,pop:0,blk:0,ind:0,si:0,di:0,wc:0})); C.forEach(c=>{const pl=[0,1,2,3,4,5].sort(()=>rnd()-.5);for(let j=0;j<3;j++)RG[c.i*3+j].res=pl[j]}); rel=C.map(()=>C.map(()=>n(rnd()*30-8)));mem=C.map(()=>C.map(()=>[]));wars=[];pend=[];logs=[];chat=[];hist=[];pacts=[];turn=1;aid=0; H=C.map((c,i)=>i==0);me=0;stats();C.forEach(c=>c.pop=c.k*1000);fx=[]; L('시스템','당신은 아르카를 이끕니다. 지역마다 특산품이 달라 자원이 부족해요 — 이웃과 교역하세요.');render()} function stats(){C.forEach(c=>{c.k=0;c.p=[0,0,0,0,0,0]});const sx=Array(NC).fill(0),sy=Array(NC).fill(0); own.forEach((o,i)=>{if(o<0)return;const c=C[o];c.k++;sx[o]+=i%GW;sy[o]+=i/GW|0;if(!RG[reg[i]].dmg)c.p[RG[reg[i]].res]++}); cen=C.map(c=>c.k?[(sx[c.i]/c.k+.5)*CS,(sy[c.i]/c.k+.5)*CS]:[0,0]); sp=C.map(c=>{const l=[];own.forEach((o,i)=>{if(o==c.i&&coast[i])l.push(i)});return l.length?[l[0],l[l.length>>1],l[l.length-1]]:[]})} function cand(a,b,am){const r=[];for(let i=0;i0&&own[i-1]==a)||(x0&&own[i-GW]==a)||(y0?i-1:-1,x0?i-GW:-1,y{if(j>=0&&own[j]>=0&&own[j]!=a)s.add(own[j])})}return[...s]} function regsOf(e){const m={};own.forEach((o,i)=>{if(o==e)m[reg[i]]=(m[reg[i]]||0)+1});return Object.keys(m).map(Number).sort((a,b)=>m[b]-m[a])} const rpos=g=>{const i=reg.findIndex((r,j)=>r==g&&own[j]==RG[g].o);const k=i<0?reg.indexOf(g):i;return[(k%GW+.5)*CS,((k/GW|0)+.5)*CS]}; const fxa=(k,p,q)=>fx.push({k,x0:p[0],y0:p[1],x1:q[0],y1:q[1],t:0,d:k=='nuke'?30:18}); // ---------- 경제·외교 ---------- const acc_=(p,f,ratio)=>C[p].blk>0||C[f].blk>0?0:cl(.3+rel[p][f]/150+C[p].tp/250+(ratio-1)*.8-(C[p].ty==6?.25:0),.05,.95); function trade(b,s,r,q,d){C[s].st[r]-=q;C[b].st[r]+=q;C[b].D-=d;C[s].D+=d;rem(b,s,'trade',3);rem(s,b,'trade',3);C[b].tp+=.5;C[s].tp+=.5} function ally(a,b){const A=C[a],B=C[b];if(A.al>=0&&B.al>=0)return 0;if(A.al<0&&B.al<0)A.al=B.al=aid++;else if(A.al<0)A.al=B.al;else B.al=A.al; L('연합',`${A.n} · ${B.n} 연합 결성/가입`);return 1} function declare(a,b,force,skip){if(wf(a,b)||!C[a].k||!C[b].k)return;const A=C[a]; if(!force&&!skip){const ids=A.al>=0?C.filter(v=>v.al==A.al&&v.i!=a&&v.i!=b&&v.k).map(v=>v.i):[...new Set([...adj(a),...adj(b)])].filter(v=>v!=a&&v!=b&&C[v].k); const vo=v=>rel[v][b]=0&&A.al==C[me].al&&H[me]&&a!=me&&ids.includes(me)){pend.push({t:'wv',to:me,a,b,ids,y0:ids.filter(v=>v!=me&&vo(v)).length});return} const yes=ids.filter(vo).length,ok=!ids.length||yes*2>=ids.length; if(a==me||b==me||H[a]||H[b])L('승인',`${A.al>=0?'연합':'주변국'} 투표 ${yes}:${ids.length-yes} → ${ok?'승인':'부결'}`); if(!ok){if(H[a]||!(A.ty==0&&rnd()<.3))return;force=true}} pacts=pacts.filter(p=>{if((p.a==a&&p.b==b)||(p.a==b&&p.b==a)){A.tp=Math.max(0,A.tp-8);rem(b,a,'brk',-30);L('조약',`${A.n}이(가) 무역협정을 파기(약속 위반)`);return false}return true}); const mk=(x,y)=>({a:x,b:y,agg:a,s:0,pu:{},k:[C[x].k,C[y].k],pre:!!force,T:turn});wars.push(mk(a,b)); if(force){A.tp=Math.max(0,A.tp-25);C.forEach(v=>{if(v.i!=a)rel[v.i][a]-=15});rem(b,a,'firststrike',-30); const t=`${A.n} → ${C[b].n} 선제공격`;hist.unshift(`T${turn} ${t}`);L('국제',`🚨 ${t}! (방어국은 승인 없이 즉시 방어 가능)`)} else{hist.unshift(`T${turn} ${A.n} → ${C[b].n} 승인 전쟁`);L('국제',`${A.n}이(가) 승인을 받아 ${C[b].n}과(와) 개전`)} snd('war'); C.forEach(v=>{if(!v.k||v.i==a||v.i==b||v.al<0)return; if(v.al==C[b].al&&!wf(a,v.i)&&(H[v.i]||rel[v.i][a]<25)){wars.push(mk(a,v.i));L('연합',`${v.n}이(가) 연합국 ${C[b].n}을(를) 위해 참전`)} else if(!force&&v.al==A.al&&v.al!=C[b].al&&!wf(v.i,b)&&rel[v.i][b]<10){wars.push(mk(v.i,b));L('연합',`${v.n}이(가) 연합국 ${A.n}을(를) 지원해 참전`)}})} function endWar(w,win){wars=wars.filter(x=>x!=w); if(win<0){L('전쟁',`${C[w.a].n}-${C[w.b].n} 전쟁이 무승부로 종결`);return} const lo=win==w.a?w.b:w.a,W=C[win],Lo=C[lo],d=n(Lo.D*.4);Lo.D-=d;W.D+=d; for(let r=0;r<6;r++){const q=Math.floor(Lo.st[r]*.4);Lo.st[r]-=q;W.st[r]+=q} Lo.mil*=.8;Lo.sh=Math.floor(Lo.sh*.7);hist.unshift(`T${turn} ${W.n} 승 / ${Lo.n} 패`); L('전쟁',`${W.n} 승리 — ${Lo.n}에게서 D ${d}와 자원 40% 획득`);snd('ok'); if(lo==w.agg){Lo.tp=Math.max(0,Lo.tp-20);L('국제',`선제공격국 ${Lo.n}의 외교 온도가 크게 하락`)} else{L('국제',`${Lo.n}은(는) 선제공격 피해국으로 외교 페널티가 없습니다.`); C.forEach(v=>{if(v.i==win||v.i==lo||!v.k)return;const al=v.al>=0&&v.al==Lo.al; if(al||(v.tp>55&&rel[v.i][lo]>=0)){if(H[v.i]){if(al)pend.push({t:'aid',to:v.i,lo});return} const g=al?200:100;if(v.Ddp){const g=take(c.i,e.i,0,1+(rnd()*3|0));c.mil*=.93;e.mil*=.95;w.s+=sg*g;m=`${c.n}의 돌격 성공: ${e.n}의 ${g}구역 점령`;fxa('boom',P0,P1)}else{c.mil*=.88;m=`${c.n}의 돌격이 ${e.n}의 방어에 막힘`}} else if(t=='n'){const ap=c.sh*(.6+rnd()*.8),dp=e.sh*(.6+rnd()*.8); if(ap>dp){e.sh=Math.floor(e.sh*.6);w.s+=sg;m=`${c.n} 함대가 ${e.n} 함대를 격파 (해상 통제)`}else{c.sh=Math.floor(c.sh*.7);m=`${c.n} 함대가 ${e.n} 함대에 패퇴`}fxa('ship',P0,P1)} else if(t=='k'){e.blk=4;m=`${c.n}이(가) ${e.n}을(를) 해상 봉쇄 — 교역·외교 수입 감소`;fxa('ship',P0,P1)} else if(t=='v'){const ap=c.mil*.8+c.sh*6,dp=e.mil*(1+e.df*.15)*(.6+rnd()*.8)*(e.sh>c.sh?1.3:1); if(ap>dp){const g=take(c.i,e.i,1,1+(rnd()*2|0));c.sh--;w.s+=sg*g;m=`${c.n}의 상륙작전 성공: ${e.n}의 ${g}구역 점령`;fxa('ship',P0,P1)}else{c.sh=Math.max(0,c.sh-2);m=`${c.n}의 상륙작전이 ${e.n}에 격퇴됨`}} else if(t=='b'){e.mil*=.82-rnd()*.05;m=`${c.n} 폭격기가 ${e.n}의 군사시설 폭격`;w.s+=sg;fxa('plane',P0,P1)} else if(t=='B'){e.ind=3;e.D=Math.max(0,e.D-80);m=`${c.n} 폭격기가 ${e.n}의 산업시설 폭격 — 수입 감소`;fxa('plane',P0,P1)} else if(t=='m'){c.ms--;const rs=regsOf(e.i);let g=sel.g;if(c.i!=me||!rs.includes(g))g=rs[c.i==me?0:rnd()*rs.length|0]; if(g==null){c.ms++;c.D+=COST[t];return -1}RG[g].dmg=3;e.mil*=.93;w.s+=sg;m=`${c.n} 미사일이 ${RG[g].n} 지역 타격 — 3턴간 생산 정지`;fxa('miss',P0,rpos(g))} else{e.mil*=.6;const g=take(c.i,e.i,0,6)||take(c.i,e.i,1,6);w.s+=sg*g;C.forEach(v=>{if(v.i!=c.i)rem(v.i,c.i,'nuke',-40)});c.tp=Math.max(0,c.tp-30); m=`☢️ ${c.n}이(가) ${e.n}에 수소폭탄 사용! 국제사회가 경악합니다`;fxa('nuke',P0,P1);snd('nuke')} if(V||t=='h'){L('전쟁',m);if(V&&t!='h')snd('war')}else CH(m);stats(); const k0=w.a==e.i?w.k[0]:w.k[1];if(wars.includes(w)&&(e.k0; if(land)o.push('a','a');if(c.sh>=1)o.push('n');if(c.sh>=3&&!land)o.push('v','v');if(c.sh>=2&&rnd()<.3)o.push('k');if(c.bm>0)o.push('b','B');if(c.ms>0)o.push('m'); if(c.ty==0&&c.mil>e.mil*3&&c.D>3000&&rnd()<.1)o.push('h'); if(!o.length||c.D<150){if(c.D>100){c.D-=100;c.df=Math.min(5,c.df+1)}return} const t=o[rnd()*o.length|0];strike(c,e,t)} // ---------- AI ---------- function aiTrade(c,pr){let r=0;c.st.forEach((s,i)=>{if(sp.k&&p.i!=c.i&&p.st[r]>q+20&&!wf(c.i,p.i)).sort((a,b)=>rel[c.i][b.i]-rel[c.i][a.i])[0]; if(cd){const d=n(q*RP[r]*(.8+rnd()*.5)); if(H[cd.i]){if(pend.length<6)pend.push({t:'req',to:cd.i,f:c.i,r,q,d})} else{const ok=rnd(){if(s>c.st[r2])r2=i}); if(c.st[r2]>70){const cd=C.filter(p=>p.k&&p.i!=c.i&&p.st[r2]<30&&p.D>150&&!wf(c.i,p.i))[0]; if(cd){const q=25,d=n(q*RP[r2]*1.1);if(H[cd.i]){if(pend.length<6)pend.push({t:'offer',to:cd.i,f:c.i,r:r2,q,d})} else if(rnd()x.a==c.i||x.b==c.i); if(w){const e=C[w.a==c.i?w.b:w.a],sc=(w.a==c.i?1:-1)*w.s;aiStrike(c,e); if(wars.includes(w)&&sc<-2&&rnd()<.35){if(H[e.i]){if(pend.length<6)pend.push({t:'peace',to:e.i,w})}else endWar(w,w.s>0?w.a:w.s<0?w.b:-1)}return} if(c.D>400&&rnd()H[v.i]&&v.k&&rel[v.i][c.i]>0);if(f&&pend.length<6)pend.push({t:'dreq',to:f.i,f:c.i})} if(rnd()v.k&&v.i!=c.i&&rel[c.i][v.i]>15&&!pacts.some(q=>(q.a==c.i&&q.b==v.i)||(q.a==v.i&&q.b==c.i))&&!wf(c.i,v.i)); if(p){if(H[p.i]){if(pend.length<6)pend.push({t:'pact',to:p.i,f:c.i})}else{pacts.push({a:c.i,b:p.i,exp:turn+10});CH(`${c.n}-${p.n} 무역협정 체결`)}}} if(rnd()v.k&&v.i!=c.i&&rel[c.i][v.i]>0&&v.D<500).sort((a,b)=>a.D-b.D)[0]; if(p&&c.D>400){c.D-=100;p.D+=100;rem(p.i,c.i,'gift',8);if(H[p.i])L('국제',`${c.n}이(가) 당신에게 D 100을 지원했습니다`);else CH(`${c.n} → ${p.n} 지원금 D 100`)}} if(rnd()3){const gr=v=>mem[c.i][v].filter(m=>m.k=='firststrike').length; const ts=C.filter(v=>v.k&&v.i!=c.i&&!wf(c.i,v.i)&&(c.sh>=3||adj(c.i).includes(v.i))&&v.mil(a.mil-gr(a.i)*30)-(b.mil-gr(b.i)*30)); if(ts.length)declare(c.i,ts[0].i,false)} if(rnd()v.k&&v.i!=c.i&&!H[v.i]&&PR[v.ty<0?1:v.ty][2]>0&&rel[c.i][v.i]>20&&rel[v.i][c.i]>20);if(p)ally(c.i,p.i)}} function next(){turn++;pend=[];wars.forEach(w=>w.pu={});stats(); C.forEach(c=>{if(!c.k)return;const f=c.k/104;c.si=n((20+c.tp*.3)*f*(c.ind>0?.6:1)); c.di=n(c.tp*1.5*f*(c.blk>0?.4:1))+pacts.filter(p=>p.a==c.i||p.b==c.i).length*8;c.wc=wars.filter(w=>w.a==c.i||w.b==c.i).length*40; c.D+=c.si+c.di-c.wc;let sh=0; for(let r=0;r<6;r++){c.st[r]+=c.p[r]-c.k*.17;if(c.st[r]<0){c.D-=n(-c.st[r]*RP[r]*.6);c.st[r]=0;sh=1}} c.pop=n(c.pop*(sh?.99:1.01));c.mil+=c.k*.04;c.tp+=(50-c.tp)*.02;c.D=Math.max(0,c.D);if(c.blk>0)c.blk--;if(c.ind>0)c.ind--}); RG.forEach(g=>{if(g.dmg>0)g.dmg--}); pacts=pacts.filter(p=>{if(p.exp>turn)return true;L('조약',`${C[p.a].n}-${C[p.b].n} 무역협정 만료`);return false}); C.forEach(ai); for(let i=0;ic.tp=cl(c.tp,0,100));stats();if(!C[me].k&&H[me]){L('시스템','국가가 멸망했습니다. 관찰 모드로 전환하세요.')}snd('turn');render()} // ---------- 저장/불러오기 ---------- function save(){const s=JSON.stringify({own,reg,RG,C,rel,mem,wars,pend,logs,chat,hist,pacts,turn,aid,H,me}); try{localStorage.setItem(SK,s)}catch(e){window.__nsSave=s}L('시스템','저장되었습니다')} function load(){let s;try{s=localStorage.getItem(SK)}catch(e){}s=s||window.__nsSave;if(!s){L('시스템','저장 데이터가 없습니다');return} const d=JSON.parse(s);({own,reg,RG,C,rel,mem,wars,pend,logs,chat,hist,pacts,turn,aid,H,me}=d);mkCoast();stats();L('시스템','불러왔습니다')} // ---------- 플레이어 행동 ---------- const can=()=>{if(!H[me]){L('시스템','관찰 중인 국가는 조작할 수 없어요 (국가 탭에서 조종 전환)');render();return false}return true}; const act={ nx(){if(!pend.length||true){next()}}, cyc(d){me=(me+d+NC)%NC;render()},spd(s){spd=s;acc=0;render()},mute(){mute=!mute;render()},save(){save();render()},load(){load();render()}, tog(){H[me]=!H[me];render()},obs(){H=H.map(()=>false);spd=1;L('시스템','관찰 모드: 모든 국가를 AI가 운영합니다');render()}, chat(){showChat=!showChat;render()},ng(){newGame()}, req(b){if(!can())return;const B=C[b],P=C[me],{r,q,d}=sel; if(P.D400&&rnd()15&&C[me].tp>40&&C[b].ty!=6&&ally(me,b))snd('ok');else{L('연합',`${C[b].n}이(가) 연합 제안을 거절`);snd('no')}render()}, pact(b){if(!can())return;if(rel[b][me]>5&&!pacts.some(p=>(p.a==me&&p.b==b)||(p.a==b&&p.b==me))&&C[b].blk<1){pacts.push({a:me,b,exp:turn+10});rem(b,me,'pact',5);L('조약',`${C[b].n}과(와) 10턴 무역협정 체결 (매턴 D+8)`);snd('ok')}else{L('조약',`${C[b].n}이(가) 무역협정을 거절/이미 체결`);snd('no')}render()}, leave(){if(!can())return;C[me].al=-1;C[me].tp-=3;L('연합','연합에서 탈퇴했습니다');render()}, help(b){if(!can())return;const P=C[me];if(P.D<150)return;P.D-=150;C[b].D+=150;P.tp+=2;rem(b,me,'aid',10);L('연합',`${C[b].n}에 지원안 시행: D 150`);render()}, war(b,f){if(!can())return;declare(me,b,f);render()}, buy(k){if(!can())return;const P=C[me],T={m:[200,'mil',12],s:[300,'sh',2],b:[250,'bm',2],x:[300,'ms',3],d:[100,'df',1]}[k];if(P.D=5))return;P.D-=T[0];P[T[1]]+=T[2];snd('click');render()}, hit(e,t){if(!can())return;const w=wf(me,e);if(!w)return;if(w.pu[me]){L('군사','이번 턴 이미 행동했습니다');return render()} w.pu[me]=1;const r=strike(C[me],C[e],t);if(r<1){w.pu[me]=0;L('군사',r<0?'수행 불가: 조건 미충족(국경·함선·폭격기·미사일)':'D가 부족합니다')}render()}, peace(e){if(!can())return;const w=wf(me,e);if(!w)return;const sa=(w.a==e?1:-1)*w.s; if(rnd()<(sa<=0?.8:.3))endWar(w,w.s>0?w.a:w.s<0?w.b:-1);else{L('외교',`${C[e].n}이(가) 평화 협정을 거절`);snd('no')}render()}, ans(i,y){const p=pend.splice(i,1)[0];if(!p)return;const P=C[p.to]; if(p.t=='req'){if(y&&P.st[p.r]>=p.q){trade(p.f,p.to,p.r,p.q,p.d);P.tp+=2;rem(p.f,p.to,'help',6);L('외교',`${C[p.f].n}의 요청 승인`)}else{rem(p.f,p.to,'refuse',-3);P.tp-=1;L('외교',`${C[p.f].n}의 요청 거절`)}} else if(p.t=='offer'){if(y&&P.D>=p.d){trade(p.to,p.f,p.r,p.q,p.d);L('외교',`${C[p.f].n}에게서 ${RN[p.r]} ${p.q}개 구매`)}} else if(p.t=='dreq'){if(y&&P.D>=150){P.D-=150;C[p.f].D+=150;P.tp+=2;rem(p.f,p.to,'help',8);L('외교',`${C[p.f].n}에 D 150 지원`)}} else if(p.t=='pact'){if(y){pacts.push({a:p.f,b:p.to,exp:turn+10});L('조약',`${C[p.f].n}과(와) 무역협정 체결`)}} else if(p.t=='peace'){if(y&&wars.includes(p.w))endWar(p.w,p.w.s>0?p.w.a:p.w.s<0?p.w.b:-1)} else if(p.t=='aid'){if(y&&P.D>=150){P.D-=150;C[p.lo].D+=150;P.tp+=3;rem(p.lo,p.to,'aid',10);L('연합',`${C[p.lo].n} 지원안 찬성: D 150`)}} else if(p.t=='wv'){const ok=(p.y0+(y?1:0))*2>=p.ids.length;L('승인',`연합 투표 ${p.y0+(y?1:0)}:${p.ids.length-p.y0-(y?1:0)} → ${ok?'승인':'부결'}`);if(ok)declare(p.a,p.b,false,true)} render()}}; const PT=p=>{const f=C[p.f]||{},T={ req:()=>`[외교 나레이션] ${f.n}에서 ${RN[p.r]} ${p.q}개를 요청했습니다. (제공 D ${p.d})`, offer:()=>`[외교 나레이션] ${f.n}이(가) ${RN[p.r]} ${p.q}개를 D ${p.d}에 팔겠다고 제안했습니다.`, dreq:()=>`[외교 나레이션] ${f.n}이(가) D 150 지원을 요청했습니다.`, pact:()=>`[조약 나레이션] ${f.n}이(가) 10턴 무역협정을 제안했습니다.`, peace:()=>`[외교 나레이션] ${C[p.w.a==p.to?p.w.b:p.w.a].n}이(가) 평화 협정을 제안했습니다.`, aid:()=>`[연합 나레이션] ${C[p.lo].n} 전후 복구 지원안(D 150)이 제출되었습니다.`, wv:()=>`[연합 나레이션] ${C[p.a].n}의 ${C[p.b].n} 전쟁 안건 투표 (현재 찬성 ${p.y0}/${p.ids.length})`};return T[p.t]()}; // ---------- 화면 ---------- const al=c=>c.al>=0?`🤝연합${c.al+1}`:''; function render(){const P=C[me]; $('nh').innerHTML=`${P.n}${H[me]?'':'(관찰)'} T${turn} 💰${n(P.D)} 🌡️${n(P.tp)} ⚔️${n(P.mil)}
`+ [[0,'⏸'],[1,'1x'],[2,'2x'],[4,'4x']].map(s=>``).join('')+``; $('np').innerHTML=pend.map((p,i)=>p.to!=me?'':`
${PT(p)}
`).join(''); $('nt').innerHTML=[['e','경제'],['d','외교'],['m','군사'],['a','연합'],['i','국가'],['l','소식']].map(t=>``).join(''); let h=''; if(tab=='e')h=`
💰 D ${n(P.D)} · 자체 +${P.si} · 외교 +${P.di} · 전쟁유지 -${P.wc}
🌡️ 온도 ${n(P.tp)} · 👥 인구 ${P.pop.toLocaleString()} · 영토 ${P.k}${P.blk>0?'
⚓ 봉쇄당함 '+P.blk+'턴':''}${P.ind>0?'
🏭 산업시설 피해 '+P.ind+'턴':''}
`+ [0,1,2,3,4,5].map(r=>`
${RN[r]} 재고 ${n(P.st[r])} · 생산 ${P.p[r]} · 소비 ${n(P.k*.17)} ${P.p[r]`).join('')+`
무역협정: ${pacts.filter(p=>p.a==me||p.b==me).map(p=>C[p.a==me?p.b:p.a].n+'('+(p.exp-turn)+'턴)').join(', ')||'없음'}
`; else if(tab=='d')h=`
거래 설정: D
`+ C.filter(c=>c.i!=me&&c.k).map(c=>{const m=mem[c.i][me].slice(-1)[0];return `
${c.n} ${wf(me,c.i)?'⚔️':''}${al(c)} · ${TY[c.ty]||'플레이어'} ${c.blk>0?'⚓':''}
🌡️${n(c.tp)} · 관계 ${rel[c.i][me]} · 군사 ${n(c.mil)}${m?` · 기억: ${MK[m.k]||m.k}(T${m.T})`:''}
`}).join(''); else if(tab=='m'){const myw=wars.filter(w=>w.a==me||w.b==me); h=`
⚔️군사 ${n(P.mil)} · 🛡️방어진지 Lv.${P.df}/5 · ⛵함선 ${P.sh} · ✈️폭격기 ${P.bm} · 🚀미사일 ${P.ms}
`+ (myw.map(w=>{const e=w.a==me?w.b:w.a,E=C[e],s=(w.a==me?1:-1)*w.s,rs=regsOf(e);return `
⚔️ vs ${E.n} ${w.agg==me?'(내가 개전)':'(방어전)'}${w.pre?' · 선제공격 전쟁':''} · 적 군사 ${n(E.mil)} ⛵${E.sh} · 전황 ${s>0?'+':''}${s}

미사일 목표:
`}).join('')||`
현재 전쟁 중이 아닙니다.
`)+ `
턴당 전쟁별 1회 행동. 돌격=국경 점령, 해상전=해상 통제, 상륙=해안 침투, 봉쇄=교역 차단, 수소폭탄=대규모 타격+국제 후폭풍.
`} else if(tab=='a'){const ids=[...new Set(C.filter(c=>c.al>=0).map(c=>c.al))]; h=(P.al>=0?`
${al(P)} 소속 · 연합 내 전쟁은 다수결, 연합국이 공격받으면 자동 참전
`+C.filter(v=>v.al==P.al&&v.i!=me).map(v=>`${v.n} D${n(v.D)} `).join('
')+`
`:`
가입한 연합이 없습니다. 외교 탭에서 연합을 제안하세요.
`)+ ids.map(id=>`
🤝 연합${id+1}: ${C.filter(c=>c.al==id).map(c=>`${c.n}`).join(', ')}
`).join('')} else if(tab=='i'){h=`
${P.n} · 성향 ${TY[P.ty]||'플레이어'} · ${H[me]?'조종 중':'AI/관찰'}
지역
`+RG.filter(g=>g.o==me).map(g=>`${g.n}: ${RN[g.res]} ${g.dmg?'(피해 '+g.dmg+'턴)':''}`).join('
')+`
전쟁 기록
${hist.slice(0,10).join('
')||'없음'}
외교 기록(상대→${P.n})
`+C.filter(c=>c.i!=me).map(c=>mem[c.i][me].slice(-2).map(m=>`${c.n}: ${MK[m.k]||m.k} ${m.v>0?'+':''}${m.v} (T${m.T})`).join('
')).filter(x=>x).join('
')+`
`} else h=``+(showChat?chat:logs).map(x=>`
${x}
`).join(''); $('nb').innerHTML=h} // ---------- 지도 그리기 ---------- function draw(){frame++;const W=GW*CS,Hh=GH*CS;ctx.fillStyle='#0c1b2e';ctx.fillRect(0,0,W,Hh); for(let i=0;i>2))&3)?'#10304f':'#164068';ctx.fillRect(X,Y,CS,CS);continue} ctx.fillStyle=C[o].c;ctx.fillRect(X,Y,CS,CS);if(coast[i]){ctx.fillStyle='#ffffff30';ctx.fillRect(X,Y,CS,CS)} const g=RG[reg[i]];if(g.dmg){ctx.fillStyle='#000a';ctx.fillRect(X,Y,CS,CS)} ctx.fillStyle=RC[g.res];ctx.fillRect(X+4,Y+4,3,3)} for(let i=0;i=0&&(own[i+1]!=own[i]||reg[i+1]!=reg[i])){ctx.strokeStyle=own[i+1]!=own[i]?(wf(own[i],own[i+1])?'#ff2a2a':'#000b'):'#fff4';ctx.lineWidth=own[i+1]!=own[i]?2:1;ctx.beginPath();ctx.moveTo((x+1)*CS,y*CS);ctx.lineTo((x+1)*CS,(y+1)*CS);ctx.stroke()} if(y=0&&(own[i+GW]!=own[i]||reg[i+GW]!=reg[i])){ctx.strokeStyle=own[i+GW]!=own[i]?(wf(own[i],own[i+GW])?'#ff2a2a':'#000b'):'#fff4';ctx.lineWidth=own[i+GW]!=own[i]?2:1;ctx.beginPath();ctx.moveTo(x*CS,(y+1)*CS);ctx.lineTo((x+1)*CS,(y+1)*CS);ctx.stroke()}} const bob=(frame>>3)&1;C.forEach(c=>{if(!c.k)return;sp[c.i].slice(0,Math.min(3,Math.ceil(c.sh/2))).forEach(i=>{if(own[i]!=c.i)return;const x=(i%GW)*CS,y=(i/GW|0)*CS;ctx.fillStyle='#fff';ctx.fillRect(x+1,y+CS-3+bob,6,2);ctx.fillRect(x+3,y+bob,2,CS-3)})}); ctx.font='bold 12px system-ui';ctx.textAlign='center';ctx.lineWidth=3;ctx.strokeStyle='#000';ctx.fillStyle='#fff'; C.forEach(c=>{if(!c.k)return;const t=(c.i==me?'★':'')+c.n+(wars.some(w=>w.a==c.i||w.b==c.i)?'⚔':'');ctx.strokeText(t,cen[c.i][0],cen[c.i][1]);ctx.fillText(t,cen[c.i][0],cen[c.i][1])}); fx=fx.filter(f=>{f.t++;const p=Math.min(1,f.t/f.d),x=f.x0+(f.x1-f.x0)*p,y=f.y0+(f.y1-f.y0)*p; if(f.k=='boom'||(f.k=='nuke'&&f.t>10)){const r=(f.t-(f.k=='nuke'?10:0))*(f.k=='nuke'?1.6:1.1);ctx.fillStyle=f.t%6<3?'#fff':'#ffb020'; for(let a=0;a<8;a++)ctx.fillRect(f.x1+Math.cos(a*.785)*r-2,f.y1+Math.sin(a*.785)*r-2,4,4);if(f.k=='nuke'){ctx.fillStyle='#ffffff'+(f.t<16?'55':'22');ctx.fillRect(0,0,W,Hh)}} else if(f.k=='miss'){ctx.strokeStyle='#ff6';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(f.x0,f.y0);ctx.lineTo(x,y);ctx.stroke();ctx.fillStyle='#fff';ctx.fillRect(x-2,y-2,5,5)} else if(f.k=='ship'||f.k=='plane'){ctx.fillStyle=f.k=='ship'?'#fff':'#ddd';ctx.fillRect(x-4,y-1,9,3);ctx.fillRect(x-1,y-4,3,9)} if(f.t>=f.d&&f.k!='boom'&&f.k!='nuke'&&f.k!='plane'){if(f.k=='miss'||f.k=='ship')fx.push({k:'boom',x0:0,y0:0,x1:f.x1,y1:f.y1,t:0,d:14})} return f.t{const r=cv.getBoundingClientRect(),x=Math.floor((e.clientX-r.left)/r.width*GW),y=Math.floor((e.clientY-r.top)/r.height*GH),i=y*GW+x; if(x<0||y<0||x>=GW||y>=GH)return;$('ni').textContent=own[i]<0?'🌊 바다':`${RG[reg[i]].n} · 소유 ${C[own[i]].n} · 특산 ${RN[RG[reg[i]].res]}${coast[i]?' · 해안':''}${RG[reg[i]].dmg?' · 피해':''}`;snd('click')}); setInterval(()=>{draw();if(spd&&!pend.length){acc+=70*spd;if(acc>=3000){acc=0;next()}}},70); window.NS={act,sel,tab:t=>{tab=t;render()}}; newGame(); })();