商海浮沉 亂世版 v3

商海浮沉 亂世版 v3

3 months ago

<!DOCTYPE html>

<html lang="zh-Hant">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>商海浮沉 — 亂世商戰 v2.0</title>

<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+TC:wght@400;600;900&family=Noto+Sans+TC:wght@300;400;700&display=swap" rel="stylesheet">

<style>

:root{

 --ink:#1a1008;--paper:#f2e8d5;--gold:#c8922a;--red:#b03020;--green:#276644;

 --blue:#1a4f72;--shadow:rgba(26,16,8,0.22);--purple:#5b2c8a;--orange:#c0620a;

 --p0:#1a4f72;--p1:#7b1e14;--p2:#276644;

}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}

body{background:var(--paper);background-image:radial-gradient(ellipse at 15% 10%,rgba(200,146,42,.1) 0%,transparent 45%),radial-gradient(ellipse at 85% 85%,rgba(176,48,32,.07) 0%,transparent 45%);font-family:'Noto Sans TC',sans-serif;color:var(--ink);min-height:100vh}

h1,h2,h3,.serif{font-family:'Noto Serif TC',serif}

.hide{display:none!important}


header{text-align:center;padding:20px 20px 12px;border-bottom:2px solid var(--gold)}

header h1{font-size:2rem;font-weight:900;letter-spacing:.2em;text-shadow:2px 2px 0 rgba(200,146,42,.2)}

header p{font-size:.76rem;color:#7a6040;letter-spacing:.12em;margin-top:3px}

.red-badge{display:inline-block;background:var(--red);color:#fff;font-size:.6rem;padding:1px 8px;border-radius:10px;letter-spacing:.1em;margin-left:6px;vertical-align:middle}

.new-badge{display:inline-block;background:var(--green);color:#fff;font-size:.6rem;padding:1px 8px;border-radius:10px;letter-spacing:.1em;margin-left:4px;vertical-align:middle}


#setup-screen{max-width:760px;margin:0 auto;padding:24px 16px 50px}

.setup-section{background:rgba(255,255,255,.6);border:1.5px solid rgba(200,146,42,.3);border-radius:12px;padding:18px 20px;margin-bottom:16px;box-shadow:0 3px 14px var(--shadow)}

.setup-section h2{font-size:.95rem;font-weight:700;color:var(--gold);letter-spacing:.12em;margin-bottom:14px;border-bottom:1px dashed rgba(200,146,42,.3);padding-bottom:6px}

.mode-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}

.mode-card{border:2px solid rgba(200,146,42,.3);border-radius:10px;padding:14px 10px;text-align:center;cursor:pointer;transition:all .2s;background:rgba(245,237,224,.7)}

.mode-card:hover,.mode-card.sel{border-color:var(--gold);background:rgba(200,146,42,.12);box-shadow:0 0 0 2px var(--gold)}

.mode-card .mc-icon{font-size:2rem;margin-bottom:6px}

.mode-card .mc-name{font-family:'Noto Serif TC',serif;font-size:.88rem;font-weight:700}

.mode-card .mc-desc{font-size:.66rem;color:#7a6040;margin-top:3px;line-height:1.4}

.rounds-row{display:flex;gap:8px;flex-wrap:wrap}

.round-btn{padding:7px 18px;border:2px solid rgba(200,146,42,.3);border-radius:8px;cursor:pointer;font-size:.8rem;font-family:'Noto Sans TC',sans-serif;font-weight:700;background:rgba(245,237,224,.7);transition:all .18s;color:var(--ink)}

.round-btn:hover,.round-btn.sel{border-color:var(--gold);background:var(--gold);color:#fff}

.player-setup-row{display:flex;flex-direction:column;gap:12px}

.psetup{border:1.5px solid rgba(200,146,42,.25);border-radius:10px;padding:12px 14px;background:rgba(255,255,255,.4)}

.psetup-header{display:flex;align-items:center;gap:10px;margin-bottom:10px}

.psetup-label{font-family:'Noto Serif TC',serif;font-size:.88rem;font-weight:700}

.chars-row{display:flex;gap:8px;flex-wrap:wrap}

.char-card{border:2px solid rgba(200,146,42,.25);border-radius:10px;padding:8px 12px;cursor:pointer;transition:all .2s;text-align:center;min-width:88px;background:rgba(245,237,224,.7)}

.char-card:hover,.char-card.sel{border-color:var(--gold);background:rgba(200,146,42,.1)}

.char-avatar{font-size:2.2rem;line-height:1}

.char-name{font-size:.68rem;margin-top:4px;font-weight:700;font-family:'Noto Serif TC',serif}

.char-trait{font-size:.6rem;color:#8a6030;margin-top:2px}

.char-bonus-preview{font-size:.58rem;color:var(--blue);margin-top:2px;line-height:1.3}

.name-input{border:1.5px solid rgba(200,146,42,.3);border-radius:6px;padding:5px 10px;font-family:'Noto Sans TC',sans-serif;font-size:.82rem;width:160px;background:rgba(255,255,255,.7);margin-left:8px}

.ai-tag{display:inline-block;background:var(--purple);color:#fff;font-size:.6rem;padding:1px 7px;border-radius:8px;margin-left:6px;letter-spacing:.06em}

.start-btn{display:block;width:100%;padding:14px;background:var(--ink);color:var(--paper);border:none;border-radius:10px;font-family:'Noto Serif TC',serif;font-size:1rem;font-weight:900;letter-spacing:.2em;cursor:pointer;transition:all .2s;margin-top:6px}

.start-btn:hover{background:#3a2810;box-shadow:0 4px 18px rgba(0,0,0,.3)}


/* Balance info box */

.balance-info{background:rgba(39,102,68,.08);border:1.5px solid rgba(39,102,68,.3);border-radius:8px;padding:10px 14px;margin-top:10px;font-size:.68rem;color:#1a4a30;line-height:1.7}

.balance-info strong{color:var(--green)}


#game-screen{max-width:1080px;margin:0 auto;padding:12px 12px 50px}

#turn-bar{text-align:center;margin-bottom:10px;padding:7px 14px;background:var(--ink);color:var(--paper);border-radius:8px;font-family:'Noto Serif TC',serif;font-size:.86rem;letter-spacing:.12em;box-shadow:0 3px 10px var(--shadow)}

#turn-bar span{color:var(--gold);font-weight:900}

.phase-ind{display:inline-block;font-size:.6rem;background:var(--purple);color:#fff;padding:1px 8px;border-radius:10px;margin-left:6px;letter-spacing:.07em}

#market-ticker{background:var(--ink);color:var(--gold);font-size:.7rem;padding:4px 14px;border-radius:6px;margin-bottom:10px;letter-spacing:.09em;font-family:'Noto Serif TC',serif;white-space:nowrap;overflow:hidden}

.players-wrap{display:grid;gap:10px;margin-bottom:12px}

.players-wrap.p2{grid-template-columns:1fr 1fr}

.players-wrap.p3{grid-template-columns:1fr 1fr 1fr}

.player-panel{border:2px solid;border-radius:10px;padding:10px 13px;background:rgba(255,255,255,.55);box-shadow:0 3px 14px var(--shadow);transition:box-shadow .3s}

.player-panel.c0{border-color:var(--p0)}.player-panel.c1{border-color:var(--p1)}.player-panel.c2{border-color:var(--p2)}

.player-panel.active-turn{box-shadow:0 0 0 3px var(--gold),0 3px 14px var(--shadow)}

.player-panel.bankrupt{opacity:.45;filter:grayscale(.8);pointer-events:none}

.pname-row{display:flex;align-items:center;gap:7px;margin-bottom:6px;flex-wrap:wrap}

.pname-avatar{font-size:1.6rem;line-height:1}

.pname-text{font-family:'Noto Serif TC',serif;font-size:.9rem;font-weight:900;letter-spacing:.07em}

.c0 .pname-text{color:var(--p0)}.c1 .pname-text{color:var(--p1)}.c2 .pname-text{color:var(--p2)}

.stat-row{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:4px}

.stat{display:flex;flex-direction:column;align-items:center;background:rgba(200,146,42,.07);border:1px solid rgba(200,146,42,.2);border-radius:5px;padding:3px 8px;min-width:56px}

.stat-label{font-size:.55rem;color:#8a7050;letter-spacing:.06em}

.stat-val{font-size:.9rem;font-weight:700;font-family:'Noto Serif TC',serif}

.sv-money{color:var(--green)}.sv-rep{color:var(--gold)}.sv-assets{color:var(--blue)}.sv-debt{color:var(--red)}.sv-staff{color:var(--purple)}.sv-sp{color:var(--orange)}

.biz-row{display:flex;flex-wrap:wrap;gap:3px;margin-top:4px}

.biz-tag{display:inline-flex;align-items:center;gap:2px;background:rgba(26,79,114,.09);border:1px solid rgba(26,79,114,.2);border-radius:4px;padding:1px 6px;font-size:.62rem}

.biz-tag.dmg{background:rgba(176,48,32,.1);border-color:var(--red);color:var(--red)}

.sbadge{font-size:.55rem;padding:1px 5px;border-radius:8px;font-weight:700}

.sb-debt{background:#ffe0d0;color:var(--red);border:1px solid var(--red)}

.sb-frozen{background:#d0e8ff;color:var(--blue);border:1px solid var(--blue)}

.sb-scandal{background:#ffd0d0;color:#900;border:1px solid #900}

.sb-bribe{background:#e8d8ff;color:var(--purple);border:1px solid var(--purple)}

.sb-special{background:rgba(192,98,10,.12);color:var(--orange);border:1px solid var(--orange)}

#event-box{border:2px solid var(--gold);border-radius:10px;padding:10px 15px;background:linear-gradient(135deg,rgba(200,146,42,.12),rgba(245,237,224,.94));margin-bottom:11px;display:none;animation:popIn .3s ease}

#event-box.danger{border-color:var(--red);background:linear-gradient(135deg,rgba(176,48,32,.1),rgba(245,237,224,.94))}

#event-box h3{font-size:.84rem;color:var(--gold);margin-bottom:4px;letter-spacing:.1em}

#event-box.danger h3{color:var(--red)}

#event-box p{font-size:.77rem;line-height:1.55}

#action-zone{background:rgba(255,255,255,.72);border:1.5px solid rgba(200,146,42,.28);border-radius:12px;padding:14px 16px;box-shadow:0 4px 18px var(--shadow);margin-bottom:12px;min-height:190px}

#action-zone h2{font-size:.95rem;font-weight:700;color:var(--gold);letter-spacing:.09em;margin-bottom:10px;border-bottom:1px dashed rgba(200,146,42,.3);padding-bottom:5px}

.tabs{display:flex;gap:5px;margin-bottom:10px;flex-wrap:wrap;align-items:center}

.tab{padding:4px 12px;border:1.5px solid rgba(200,146,42,.35);border-radius:6px;background:rgba(255,255,255,.6);cursor:pointer;font-size:.72rem;font-family:'Noto Sans TC',sans-serif;letter-spacing:.06em;transition:all .16s;color:var(--ink)}

.tab:hover,.tab.active{background:var(--gold);color:#fff;border-color:var(--gold)}

.tab.sp-tab{border-color:var(--orange);color:var(--orange)}

.tab.sp-tab:hover,.tab.sp-tab.active{background:var(--orange);color:#fff;border-color:var(--orange)}

.skip-btn{margin-left:auto;padding:4px 12px;border:1.5px solid #888;border-radius:6px;background:transparent;cursor:pointer;font-size:.7rem;font-family:'Noto Sans TC',sans-serif;color:#666;transition:all .16s}

.skip-btn:hover{background:#555;color:#fff;border-color:#555}

.card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(148px,1fr));gap:9px}

.card{border:1.5px solid rgba(200,146,42,.3);border-radius:9px;padding:10px 11px;background:rgba(245,237,224,.9);cursor:pointer;transition:transform .16s,box-shadow .16s,border-color .16s;position:relative;overflow:hidden}

.card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--gold);opacity:.4}

.card:hover:not(.disabled){transform:translateY(-2px);box-shadow:0 7px 20px var(--shadow);border-color:var(--gold)}

.card:hover:not(.disabled)::before{opacity:1}

.card.dng{border-color:rgba(176,48,32,.35)}.card.dng::before{background:var(--red)}

.card.sp-card{border-color:rgba(192,98,10,.45)}.card.sp-card::before{background:var(--orange)}

.card.sp-card:hover:not(.disabled){border-color:var(--orange)}

.card.disabled{opacity:.35;pointer-events:none}

.card.owned-card::after{content:'已持有';position:absolute;top:5px;right:5px;font-size:.56rem;background:var(--green);color:#fff;padding:1px 5px;border-radius:3px}

.ci{font-size:1.4rem;margin-bottom:3px}

.ct{font-family:'Noto Serif TC',serif;font-size:.8rem;font-weight:700;margin-bottom:2px}

.cd{font-size:.63rem;color:#5a4a30;line-height:1.45}

.cc{font-size:.63rem;color:var(--red);font-weight:700;margin-top:3px}

.cr{font-size:.63rem;color:var(--green);font-weight:700}

.cq{font-size:.6rem;color:var(--purple);margin-top:1px}

.cop{font-size:.6rem;color:var(--orange);font-weight:700;margin-top:2px}

.plabel{display:inline-block;font-size:.62rem;padding:2px 8px;border-radius:12px;background:var(--gold);color:#fff;letter-spacing:.09em;margin-bottom:8px;font-family:'Noto Serif TC',serif}

.plabel-sp{background:var(--orange)}

.mtable{width:100%;border-collapse:collapse;font-size:.73rem}

.mtable th{background:rgba(200,146,42,.1);padding:5px 9px;text-align:left;font-family:'Noto Serif TC',serif;letter-spacing:.06em;border-bottom:1.5px solid rgba(200,146,42,.28)}

.mtable td{padding:6px 9px;border-bottom:1px dashed rgba(200,146,42,.13);vertical-align:middle}

.mtable tr:not(.cant):hover td{background:rgba(200,146,42,.06);cursor:pointer}

.mtable tr.cant td{opacity:.35;pointer-events:none}

.tu{color:var(--green);font-weight:700}.td{color:var(--red);font-weight:700}.tf{color:#888}

.tip{font-size:.64rem;color:#8a7050;margin-top:7px;font-style:italic}

#log-box{border:1px solid rgba(200,146,42,.2);border-radius:7px;background:rgba(255,255,255,.45);padding:8px 12px;max-height:110px;overflow-y:auto;font-size:.69rem;line-height:1.65}

.le{border-bottom:1px dashed rgba(200,146,42,.12);padding:1px 0}.le:last-child{border-bottom:none}

.lp0{color:var(--p0)}.lp1{color:var(--p1)}.lp2{color:var(--p2)}.lev{color:var(--gold)}.lwn{color:var(--red)}.lsy{color:var(--purple)}.lai{color:#555;font-style:italic}.lsp{color:var(--orange);font-weight:700}

#log-box::-webkit-scrollbar{width:3px}

#log-box::-webkit-scrollbar-thumb{background:rgba(200,146,42,.35);border-radius:2px}

.btn{display:inline-block;padding:6px 14px;font-family:'Noto Sans TC',sans-serif;font-size:.76rem;border:none;border-radius:6px;cursor:pointer;letter-spacing:.07em;font-weight:700;transition:all .16s}

.btn-ink{background:var(--ink);color:var(--paper)}.btn-ink:hover{background:#2a1e0a}

.btn-gold{background:var(--gold);color:#fff}.btn-gold:hover{background:#a07020}

.btn-red{background:var(--red);color:#fff}.btn-red:hover{background:#8a2010}

.modal-ov{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.58);z-index:999;display:flex;align-items:center;justify-content:center;animation:fadeIn .18s}

.modal{background:var(--paper);border:2px solid var(--gold);border-radius:13px;padding:22px 26px;max-width:420px;width:90%;box-shadow:0 16px 50px rgba(0,0,0,.38);animation:popIn .28s ease}

.modal h3{font-size:1rem;font-weight:900;margin-bottom:8px;letter-spacing:.1em}

.modal p{font-size:.79rem;line-height:1.68;color:#3a2a10;margin-bottom:12px}

.modal-btns{display:flex;gap:7px;justify-content:flex-end}

.ai-thinking{text-align:center;padding:18px;font-family:'Noto Serif TC',serif;font-size:.88rem;color:var(--gold);letter-spacing:.1em;animation:pulse 1s infinite}

@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}

#result-screen{display:none;text-align:center;padding:24px 16px;max-width:800px;margin:0 auto}

#result-screen.show{display:block;animation:popIn .4s ease}

#result-screen h2{font-size:1.8rem;font-weight:900;margin-bottom:8px;letter-spacing:.15em}

.score-grid{display:grid;gap:12px;margin:16px auto;max-width:700px}

.score-grid.sg2{grid-template-columns:1fr 1fr}

.score-grid.sg3{grid-template-columns:1fr 1fr 1fr}

.score-card{border:2px solid var(--gold);border-radius:10px;padding:13px 18px;position:relative}

.score-card.winner-card{border-color:var(--gold);background:rgba(200,146,42,.08);box-shadow:0 0 0 3px var(--gold)}

.sc-rank{position:absolute;top:-10px;left:50%;transform:translateX(-50%);font-size:.62rem;background:var(--gold);color:#fff;padding:1px 10px;border-radius:10px;font-family:'Noto Serif TC',serif;font-weight:700}

.sc-total{font-size:1.6rem;font-weight:900;font-family:'Noto Serif TC',serif;margin:5px 0}

.sc-break{font-size:.65rem;color:#7a6040;line-height:1.75;margin-top:3px}

/* Merchant 3rd action indicator */

.action-3rd{display:inline-block;font-size:.6rem;background:rgba(176,48,32,.15);color:var(--red);border:1px solid rgba(176,48,32,.35);padding:1px 8px;border-radius:8px;margin-left:6px}

/* Char balance tooltip */

.char-balance{font-size:.58rem;padding:5px 8px;background:rgba(39,102,68,.08);border-radius:5px;margin-top:5px;color:#1a4a30;line-height:1.5;border-left:2px solid var(--green)}

@keyframes popIn{from{transform:scale(.93);opacity:0}to{transform:scale(1);opacity:1}}

@keyframes fadeIn{from{opacity:0}to{opacity:1}}

@keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-5px)}40%{transform:translateX(5px)}60%{transform:translateX(-3px)}80%{transform:translateX(3px)}}

@media(max-width:600px){

.players-wrap.p3{grid-template-columns:1fr}

.mode-grid{grid-template-columns:1fr}

.card-grid{grid-template-columns:1fr 1fr}

.score-grid.sg3{grid-template-columns:1fr}

header h1{font-size:1.45rem}

}

</style>

</head>

<body>

<header>

 <h1>商海浮沉<span class="red-badge">亂世版</span><span class="new-badge">v2.0 平衡</span></h1>

 <p>商業對弈 · 六大角色各顯神通 · 平衡重製版</p>

</header>


<div id="setup-screen">

 <div class="setup-section">

  <h2>⚔️ 選擇對戰模式</h2>

  <div class="mode-grid">

   <div class="mode-card sel" data-mode="pvc" onclick="selMode('pvc',this)">

    <div class="mc-icon">🧠</div><div class="mc-name">人機對戰</div>

    <div class="mc-desc">單人挑戰電腦<br>AI 全力最優解</div>

   </div>

   <div class="mode-card" data-mode="pvp" onclick="selMode('pvp',this)">

    <div class="mc-icon">⚔️</div><div class="mc-name">雙人對戰</div>

    <div class="mc-desc">兩位玩家同台<br>面對面決一高下</div>

   </div>

   <div class="mode-card" data-mode="3p" onclick="selMode('3p',this)">

    <div class="mc-icon">🔱</div><div class="mc-name">三人亂局</div>

    <div class="mc-desc">三方同場角力<br>聯盟與背叛並存</div>

   </div>

  </div>

 </div>


 <div class="setup-section">

  <h2>⏳ 選擇回合數</h2>

  <div class="rounds-row">

   <div class="round-btn" onclick="selRounds(8,this)">8回合(快局)</div>

   <div class="round-btn sel" onclick="selRounds(12,this)">12回合(標準)</div>

   <div class="round-btn" onclick="selRounds(16,this)">16回合(長局)</div>

   <div class="round-btn" onclick="selRounds(20,this)">20回合(史詩)</div>

  </div>

 </div>


 <div class="setup-section">

  <h2>🎭 配置商人</h2>

  <div class="balance-info">

   ⚖️ <strong>v2.0 平衡說明</strong>:老商人第3次行動受限(僅限投資/升級,且有風險懲罰),信譽上限降至8。其他角色新增專屬被動與主動特技,詳見下方角色說明。

  </div>

  <div class="player-setup-row" id="player-setup-rows"></div>

 </div>


 <button class="start-btn" onclick="startGame()">🚀 開始商戰</button>

</div>


<div id="game-screen" class="hide">

 <div id="turn-bar">第 <span id="tn">1</span> 回合 / <span id="tm">12</span> &nbsp;|&nbsp; 行動者:<span id="cp">—</span><span class="phase-ind" id="pi">行動中</span></div>

 <div id="market-ticker">📊 載入中…</div>

 <div class="players-wrap" id="players-wrap"></div>

 <div id="event-box"><h3 id="et"></h3><p id="ed"></p></div>

 <div id="action-zone"><h2 id="at">⚖️</h2><div id="ac"></div></div>

 <div id="log-box"></div>

</div>


<div id="result-screen">

 <h2 class="serif">📜 塵埃落定</h2>

 <p style="color:#7a6040;font-size:.85rem;margin-bottom:6px;letter-spacing:.1em">商海博弈,勝負已判</p>

 <div class="score-grid" id="score-grid"></div>

 <div id="winner-msg" style="font-size:1.15rem;font-weight:900;font-family:'Noto Serif TC',serif;margin:10px 0;letter-spacing:.15em"></div>

 <button class="btn btn-ink" onclick="goSetup()" style="margin-top:6px">重新開局</button>

</div>


<script>

// ═══════════════════════════════════════════════════

// CHARS — 平衡重製版

// merchant: 3rd action restricted + rep cap 8

// noble:  +$200/turn passive + 30% tactic block

// scholar: free market preview every 2 turns + invest +30%

// pirate:  +$150/turn smuggle + tactic -25% + 20% crit

// madame:  intercept tactic 3x/game + ally effect +50%

// general: sabotage +50% + extortion ability

// ═══════════════════════════════════════════════════

const CHARS = [

 { id:'merchant', avatar:'🧔', name:'老商人',  color:'#1a4f72', trait:'穩健老練(弱化)',

  bonus:'每回合3次行動,但第3次僅限投資/升級,有15%懲罰風險,信譽上限8',

  balance:'⚠️ 已弱化:第3次行動受限、信譽天花板較低' },

 { id:'noble',  avatar:'👘', name:'世家公子', color:'#7b1e14', trait:'信譽加成',

  bonus:'初始信譽+2,每回合被動獲得$200家族分潤,信譽≥6時有30%機率抵擋謀略攻擊',

  balance:'✅ 強化:被動收入+護盾機制,中後期穩健' },

 { id:'scholar', avatar:'🎓', name:'儒商才子', color:'#276644', trait:'投資眼光',

  bonus:'投資回報+30%,每偶數回合可免費預判下回合市場事件(免費內線)',

  balance:'✅ 強化:投資更準更賺,免費情報每2回合一次' },

 { id:'pirate',  avatar:'🏴‍☠️',name:'海商梟雄', color:'#5b2c8a', trait:'風險偏好',

  bonus:'每回合固定獲$150走私收入,謀略費用-25%,謀略行動20%機率暴擊(效果×1.5)',

  balance:'✅ 強化:固定收入+謀略折扣+暴擊系統' },

 { id:'madame',  avatar:'👩‍💼',name:'女強人',  color:'#c0620a', trait:'談判高手',

  bonus:'聯盟/公關效果+50%,每局3次「斡旋談判」——支付$300可阻止對手一個謀略行動',

  balance:'✅ 強化:新增防禦性主動技能,談判保護自己' },

 { id:'general', avatar:'⚔️', name:'武將商賈', color:'#555',  trait:'強硬手腕',

  bonus:'破壞手段效果+50%,可使用「威嚇索費」——向信譽比自己低的對手強收$400保護費',

  balance:'✅ 強化:破壞更猛,新增主動威嚇技能' },

];


const BUSINESSES = [

 {id:'stall',  icon:'🥟',name:'夜市攤位', cost:500, income:180,rep:0,staff:1,desc:'低門檻現金流,抗風險能力弱。',riskId:'fire'},

 {id:'store',  icon:'🏪',name:'零售商店', cost:1000,income:320,rep:1,staff:2,desc:'社區零售,穩健但競爭大。',riskId:'theft'},

 {id:'factory', icon:'🏭',name:'製造工廠', cost:2200,income:600,rep:1,staff:5,desc:'產能大,勞資糾紛風險高。',riskId:'strike'},

 {id:'hotel',  icon:'🏨',name:'商務旅館', cost:3500,income:950,rep:2,staff:8,desc:'現金流強,評價醜聞影響大。',riskId:'scandal'},

 {id:'tech',  icon:'💻',name:'科技新創', cost:3000,income:800,rep:1,staff:4,desc:'高波動高成長,市場事件敏感。',riskId:'hack'},

 {id:'media',  icon:'📺',name:'媒體集團', cost:2500,income:650,rep:3,staff:3,desc:'影響力廣,可操控輿論。',riskId:'lawsuit'},

 {id:'finance', icon:'🏦',name:'財務公司', cost:4500,income:1200,rep:2,staff:3,desc:'利潤最豐,受利率衝擊重。',riskId:'audit'},

 {id:'export', icon:'🚢',name:'出口貿易', cost:1800,income:500,rep:1,staff:2,desc:'收益受匯率影響,開放市場有加成。',riskId:'tariff'},

];


const BIZ_UPGRADES = {

 stall:  [{cost:300,incomeAdd:120,perk:'擴攤位,員工+1',staffAdd:1,special:null},{cost:600,incomeAdd:200,perk:'品牌化,信譽+1',staffAdd:0,special:'rep+1'},{cost:1000,incomeAdd:300,perk:'連鎖攤位,收入再+200',staffAdd:1,special:'chain'}],

 store:  [{cost:500,incomeAdd:150,perk:'裝潢翻新,信譽+1',staffAdd:0,special:'rep+1'},{cost:900,incomeAdd:250,perk:'自有品牌,員工+2',staffAdd:2,special:null},{cost:1500,incomeAdd:400,perk:'旗艦店,每回合額外$300',staffAdd:1,special:'flagship'}],

 factory: [{cost:800,incomeAdd:200,perk:'購入設備,產能+$200',staffAdd:0,special:null},{cost:1500,incomeAdd:350,perk:'自動化,員工-3節省工資',staffAdd:-3,special:'auto'},{cost:2500,incomeAdd:500,perk:'出口許可,出口加成+50%',staffAdd:0,special:'export_lic'}],

 hotel:  [{cost:1000,incomeAdd:250,perk:'增建客房,員工+3',staffAdd:3,special:null},{cost:2000,incomeAdd:450,perk:'五星評級,信譽+2',staffAdd:0,special:'rep+2'},{cost:3000,incomeAdd:700,perk:'豪華套房,收入翻倍加成',staffAdd:2,special:'luxury'}],

 tech:  [{cost:800,incomeAdd:300,perk:'研發投入,下回合投資+10%',staffAdd:1,special:'inv_bonus'},{cost:1500,incomeAdd:500,perk:'專利技術,競爭對手無法破壞',staffAdd:0,special:'patent'},{cost:2500,incomeAdd:800,perk:'IPO上市,立即獲得$2000',staffAdd:2,special:'ipo'}],

 media:  [{cost:600,incomeAdd:200,perk:'擴大發行,信譽+1',staffAdd:1,special:'rep+1'},{cost:1200,incomeAdd:350,perk:'輿論操控,謀略費用-10%',staffAdd:0,special:'tac_disc'},{cost:2000,incomeAdd:550,perk:'媒體壟斷,每回合可免費抹黑一次',staffAdd:1,special:'free_smear'}],

 finance: [{cost:1200,incomeAdd:300,perk:'擴展業務,信譽+1',staffAdd:1,special:'rep+1'},{cost:2500,incomeAdd:600,perk:'基金管理,投資回報+15%',staffAdd:0,special:'fund'},{cost:4000,incomeAdd:900,perk:'跨國業務,每回合自動獲利$500',staffAdd:2,special:'intl'}],

 export: [{cost:500,incomeAdd:180,perk:'新航線,員工+1',staffAdd:1,special:null},{cost:1000,incomeAdd:300,perk:'多市場布局,匯率影響減半',staffAdd:0,special:'fx_hedge'},{cost:1800,incomeAdd:500,perk:'自由貿易協定,出口收入+40%',staffAdd:1,special:'fta'}],

};


const BIZ_RISKS = {

 fire:  {name:'意外失火', effect:(p,b)=>{p.damagedBiz=b.id; return `${b.name}意外失火,本回合收入歸零!`}},

 theft: {name:'員工偷竊', effect:(p,b)=>{p.damagedBiz=b.id; return `${b.name}遭內部盜竊,本回合收入減半!`}},

 strike: {name:'工人罷工', effect:(p,b)=>{p.damagedBiz=b.id; p.money=Math.max(0,p.money-300); return `${b.name}工人罷工!損失$300且本回合停業`}},

 scandal:{name:'服務醜聞', effect:(p,b)=>{p.rep=Math.max(1,p.rep-2); p.scandal=true; return `${b.name}負評爆發,信譽-2!`}},

 hack:  {name:'資安入侵', effect:(p,b)=>{p.money=Math.max(0,p.money-800); return `${b.name}被駭客入侵,損失$800修復費`}},

 lawsuit:{name:'版權訴訟', effect:(p,b)=>{p.money=Math.max(0,p.money-600); p.rep=Math.max(1,p.rep-1); return `${b.name}收律師函,罰款$600,信譽-1`}},

 audit: {name:'稽查審計', effect:(p,b)=>{p.frozen=true; return `${b.name}遭監管審查,資金凍結一回合!`}},

 tariff: {name:'關稅壁壘', effect:(p,b)=>{p.damagedBiz=b.id; return `${b.name}遭關稅壁壘,本回合出口停滯!`}},

};


const INVESTMENTS = [

 {id:'stock', icon:'📈',name:'股票',  cost:400, vol:0.9, base:0.35,desc:'高波動,趨勢影響大。'},

 {id:'bond', icon:'📜',name:'債券',  cost:700, vol:0.1, base:0.18,desc:'穩定低回報,升息時虧損。'},

 {id:'reale', icon:'🏗️',name:'房地產', cost:2000,vol:0.5, base:0.30,desc:'長線資產,景氣循環敏感。'},

 {id:'gold', icon:'🥇',name:'黃金',  cost:900, vol:0.3, base:0.20,desc:'避險資產,危機時增值。'},

 {id:'crypto',icon:'₿', name:'加密貨幣',cost:600, vol:1.8, base:0.50,desc:'極度波動,可一夜暴富或歸零。'},

 {id:'vc',  icon:'🦄',name:'創投基金',cost:1500,vol:1.2, base:0.60,desc:'押注新創,輸贏均極端。'},

];


const EVENTS = [

 {title:'🌪️ 全球衰退',  desc:'蕭條期,所有商業收入-40%,借貸成本加重。', effect:'recession',  danger:true },

 {title:'🎊 消費熱潮',  desc:'節日帶動全面消費,所有收入+50%。',     effect:'boom',     danger:false},

 {title:'📰 媒體醜聞',  desc:'謠言四起,信譽低於3者損失$500。',      effect:'media_scandal',danger:true },

 {title:'🏛️ 政策補貼',  desc:'政府補助中小企,每人獲$400。',       effect:'subsidy',    danger:false},

 {title:'💸 惡性通膨',  desc:'物價飛漲!購買成本+30%,工資上漲。',    effect:'inflation',  danger:true },

 {title:'⚖️ 貿易開放',  desc:'新市場,信譽≥4者額外獲$600商機。',     effect:'trade_open',  danger:false},

 {title:'🔥 投機狂熱',  desc:'泡沫期,股票與加密貨幣收益加倍。',     effect:'invest_boom', danger:false},

 {title:'📉 金融海嘯',  desc:'崩盤!所有投資虧損50%本金。',        effect:'crash',    danger:true },

 {title:'⚡ 科技浪潮',  desc:'科技新創估值暴漲,持有者獲$1000紅利。',   effect:'tech_wave',  danger:false},

 {title:'🏦 央行升息',  desc:'利率升,債券虧損,財務公司收入-30%。',   effect:'rate_up',   danger:true },

 {title:'🌧️ 勞資糾紛',  desc:'罷工潮,每員工損失$80。',         effect:'labor_unrest', danger:true },

 {title:'🌏 匯率震盪',  desc:'出口商收入×2,但全體進口成本+15%。',    effect:'fx_shock',   danger:false},

 {title:'🔍 政府查稅',  desc:'資產>$8000者損失10%。',          effect:'tax_raid',   danger:true },

 {title:'💡 創新浪潮',  desc:'科技與媒體公司收入+60%。',         effect:'innovation',  danger:false},

 {title:'🤝 市場平穩',  desc:'無重大事件,各商人按計劃推進。',       effect:'none',     danger:false},

];


const TACTICS = [

 {id:'poach',  icon:'🕵️',name:'挖角人才', cost:500, repReq:0,staffReq:0, desc:'搶走對手2名員工,降低其產業效率。',danger:false},

 {id:'smear',  icon:'🗞️',name:'黑色公關', cost:700, repReq:0,staffReq:0, desc:'散布謠言:目標信譽-2,自身信譽-1,醜聞纏身。',danger:true},

 {id:'pr',    icon:'📢',name:'公關攻勢', cost:600, repReq:0,staffReq:0, desc:'砸錢建立形象,信譽+2,清除醜聞狀態。',danger:false},

 {id:'bribe',  icon:'💼',name:'疏通關係', cost:900, repReq:0,staffReq:0, desc:'賄賂監管,下回合免疫所有隨機事件懲罰。',danger:true},

 {id:'loan',   icon:'🏦',name:'槓桿借貸', cost:0,  repReq:2,staffReq:0, desc:'借入$2000(信譽-1,下回合須還$2400)。',danger:true},

 {id:'highloan', icon:'💳',name:'高利貸',  cost:0,  repReq:0,staffReq:0, desc:'急借$3000(信譽-2,下回合須還$4000)。',danger:true},

 {id:'sabotage', icon:'🔧',name:'暗中破壞', cost:1000,repReq:0,staffReq:0, desc:'摧毀對手一間產業,當回合收入歸零。',danger:true},

 {id:'insider', icon:'🔮',name:'內線消息', cost:800, repReq:3,staffReq:0, desc:'提前獲知下回合市場事件,搶先佈局。',danger:false},

 {id:'recruit', icon:'👥',name:'大規模招募', cost:400, repReq:0,staffReq:0, desc:'招募3名員工,每人每回合貢獻$150收益(扣工資後)。',danger:false},

 {id:'monopoly', icon:'🎯',name:'壟斷定價', cost:1200,repReq:4,staffReq:5, desc:'強制提高售價,本回合產業收入+15%,信譽-1。',danger:true},

 {id:'merger',  icon:'🤝',name:'策略聯盟', cost:500, repReq:2,staffReq:0, desc:'提議聯盟,雙方本回合收入+20%(需對方同意)。',danger:false},

 {id:'fire_sale',icon:'🔥',name:'甩賣資產', cost:0,  repReq:0,staffReq:0, desc:'急售一間產業,回收60%成本現金,信譽-1。',danger:true},

];


// ═══════════════════════════════════════════════════

// SPECIAL ABILITIES(角色專屬特技)

// ═══════════════════════════════════════════════════

const SPECIAL_ABILITIES = {

 // 女強人:斡旋談判 — 支付$300阻止對手下一個謀略行動

 madame_intercept: {

  id:'madame_intercept', icon:'🛡️', name:'斡旋談判',

  desc:'支付$300,阻止一名對手下一個謀略行動無效化。每局限用3次。',

  cost:300, maxUses:3,

 },

 // 武將商賈:威嚇索費 — 向信譽比自己低的對手收保護費$400

 general_extort: {

  id:'general_extort', icon:'⚔️', name:'威嚇索費',

  desc:'向信譽比自己低的對手強收$400保護費(對手拒絕將損失信譽-1)。每回合限用1次。',

  cost:0, maxUses:99,

 },

};


// ═══════════════════════════════════════════════════

// SETUP STATE

// ═══════════════════════════════════════════════════

let setupCfg = { mode:'pvc', rounds:12, players:[] };

let marketTrends = {};

let G = null;


function selMode(m, el){

 setupCfg.mode = m;

 document.querySelectorAll('.mode-card').forEach(e=>e.classList.remove('sel'));

 el.classList.add('sel');

 renderPlayerSetup();

}

function selRounds(r, el){

 setupCfg.rounds = r;

 document.querySelectorAll('.round-btn').forEach(e=>e.classList.remove('sel'));

 el.classList.add('sel');

}

function renderPlayerSetup(){

 const cnt = setupCfg.mode==='3p'?3:2;

 const isAI = setupCfg.mode==='pvc';

 const rows = document.getElementById('player-setup-rows');

 rows.innerHTML='';

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

  const isAIPlayer = isAI && i>0;

  const defaultChar = CHARS[i];

  if(!setupCfg.players[i]) setupCfg.players[i]={char:defaultChar.id, name:''};

  const div=document.createElement('div'); div.className='psetup';

  div.innerHTML=`<div class="psetup-header">

   <span class="psetup-label" style="color:${defaultChar.color}">商人 ${i+1}${isAIPlayer?'<span class="ai-tag">AI</span>':''}</span>

   ${!isAIPlayer?`<input class="name-input" id="pname-${i}" placeholder="輸入名稱(可選)" value="${setupCfg.players[i].name||''}">`:

    `<span style="font-size:.78rem;color:var(--purple);margin-left:8px">🤖 電腦全力出擊</span>`}

  </div>

  <div class="chars-row" id="chars-${i}">

   ${CHARS.map(c=>`<div class="char-card${setupCfg.players[i].char===c.id?' sel':''}" onclick="selChar(${i},'${c.id}',this)">

    <div class="char-avatar">${c.avatar}</div>

    <div class="char-name">${c.name}</div>

    <div class="char-trait">${c.trait}</div>

    <div class="char-balance">${c.balance}</div>

   </div>`).join('')}

  </div>`;

  rows.appendChild(div);

 }

}

function selChar(pidx, cid, el){

 setupCfg.players[pidx].char = cid;

 document.querySelectorAll(`#chars-${pidx} .char-card`).forEach(e=>e.classList.remove('sel'));

 el.classList.add('sel');

}


function startGame(){

 const cnt = setupCfg.mode==='3p'?3:2;

 const isAI = setupCfg.mode==='pvc';

 const players=[];

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

  const cfg = setupCfg.players[i]||{char:CHARS[i].id,name:''};

  const ch = CHARS.find(c=>c.id===cfg.char)||CHARS[i];

  const nameEl = document.getElementById(`pname-${i}`);

  const rawName = nameEl?nameEl.value.trim():'';

  const name = rawName || ch.name+(cnt>2?` ${i+1}`:'');

  const isMerchant = ch.id==='merchant';

  const isNoble  = ch.id==='noble';

  const isScholar = ch.id==='scholar';

  const isPirate  = ch.id==='pirate';

  const isMadame  = ch.id==='madame';

  const isGeneral = ch.id==='general';

  players.push({

   idx:i, name, avatar:ch.avatar, color:ch.color,

   charId:ch.id, charBonus:ch.bonus,

   isAI: isAI&&i>0,

   money:4000, debt:0,

   rep: isNoble?5:3,

   repCap: isMerchant?8:10, // ← 老商人信譽天花板

   assets:0, staff:0,

   businesses:[], bizLevels:{}, bizPerks:[],

   damagedBiz:null, frozen:false, scandal:false,

   loans:[], bribeActive:false, monopoly:false, hasMerger:[],

   // passive flags

   passiveIncome: isNoble?200:isPirate?150:0,    // 每回合被動收入

   investBonus: isScholar?0.30:0,          // scholar +30%

   tacticDiscount: isPirate?0.25:0,         // pirate -25%

   tacticCrit: isPirate?0.20:0,           // pirate 20%暴擊

   allyBonus: isMadame?0.5:0,            // madame +50%

   sabBonus: isGeneral?0.50:0,            // general +50%

   nobleShield: isNoble,               // noble 信譽護盾

   scholarPreview: isScholar,            // scholar 偶數回合免費情報

   merchantExtraAction: isMerchant,         // 第3次受限行動

   extraAction: isMerchant?1:0,

   // special ability charges

   mInterceptLeft: isMadame?3:0,

   mInterceptActive: false, // 被疏通的對手下次謀略無效

   gExtortUsedThisTurn: false,

   bankrupted:false,

  });

 }

 G = {

  turn:1, maxTurns:setupCfg.rounds,

  playerCount:cnt, players,

  curPlayer:0, actionsLeft:2,

  currentEvent:null, eventMod:'none',

  log:[], nextEventPreview:null,

  merchantThirdAction:false, // true表示老商人正在用第3次(受限)行動

 };

 for(const k of ['stock','bond','reale','gold','crypto','vc']) marketTrends[k]=0;

 document.getElementById('setup-screen').classList.add('hide');

 document.getElementById('result-screen').style.display='none';

 document.getElementById('game-screen').classList.remove('hide');

 buildPlayerPanels();

 updateUI();

 startTurn();

}

function goSetup(){

 document.getElementById('result-screen').style.display='none';

 document.getElementById('game-screen').classList.add('hide');

 document.getElementById('setup-screen').classList.remove('hide');

}


// ═══════════════════════════════════════════════════

// UI

// ═══════════════════════════════════════════════════

function $(id){return document.getElementById(id)}

function buildPlayerPanels(){

 const wrap = $('players-wrap');

 wrap.className='players-wrap '+(G.playerCount===2?'p2':'p3');

 wrap.innerHTML='';

 G.players.forEach((p,i)=>{

  const div=document.createElement('div');

  div.id=`pp-${i}`; div.className=`player-panel c${i}`;

  // special ability indicator

  const spLabel = p.mInterceptLeft>0?`<div class="stat"><span class="stat-label">斡旋</span><span class="stat-val sv-sp" id="sp-${i}">${p.mInterceptLeft}次</span></div>`:

          p.charId==='general'?`<div class="stat"><span class="stat-label">威嚇</span><span class="stat-val sv-sp" id="sp-${i}">✅</span></div>`:'';

  div.innerHTML=`<div class="pname-row"><span class="pname-avatar">${p.avatar}</span><span class="pname-text">${p.name}</span><span id="pbadge-${i}" style="display:flex;gap:3px;flex-wrap:wrap;margin-left:4px"></span></div>

  <div class="stat-row">

   <div class="stat"><span class="stat-label">資金</span><span class="stat-val sv-money" id="pm-${i}">$0</span></div>

   <div class="stat"><span class="stat-label">債務</span><span class="stat-val sv-debt" id="pd-${i}">$0</span></div>

   <div class="stat"><span class="stat-label">資產</span><span class="stat-val sv-assets" id="pa-${i}">$0</span></div>

   <div class="stat"><span class="stat-label">信譽</span><span class="stat-val sv-rep" id="pr-${i}">⭐3</span></div>

   <div class="stat"><span class="stat-label">員工</span><span class="stat-val sv-staff" id="ps-${i}">0</span></div>

   ${spLabel}

  </div>

  <div class="biz-row" id="pb-${i}"></div>`;

  wrap.appendChild(div);

 });

}


function updateUI(){

 $('tn').textContent=G.turn; $('tm').textContent=G.maxTurns;

 const cp=G.players[G.curPlayer];

 $('cp').textContent=cp.avatar+' '+cp.name+(cp.isAI?' 🤖':'');

 $('pi').textContent=cp.isAI?'AI思考中':'行動中';

 G.players.forEach((p,i)=>{

  $(`pm-${i}`).textContent='$'+p.money.toLocaleString();

  $(`pd-${i}`).textContent='$'+p.debt.toLocaleString();

  $(`pa-${i}`).textContent='$'+p.assets.toLocaleString();

  $(`pr-${i}`).textContent='⭐'+p.rep+(p.repCap<10?`/${p.repCap}`:'');

  $(`ps-${i}`).textContent=p.staff+'人';

  // special ability display

  const spEl = $(`sp-${i}`);

  if(spEl){

   if(p.charId==='madame') spEl.textContent=p.mInterceptLeft+'次';

   if(p.charId==='general') spEl.textContent=p.gExtortUsedThisTurn?'⏳':'✅';

  }

  let bd='';

  if(p.debt>0) bd+=`<span class="sbadge sb-debt">債$${p.debt}</span>`;

  if(p.frozen) bd+=`<span class="sbadge sb-frozen">凍結</span>`;

  if(p.scandal) bd+=`<span class="sbadge sb-scandal">醜聞</span>`;

  if(p.bribeActive) bd+=`<span class="sbadge sb-bribe">疏通</span>`;

  if(p.mInterceptActive) bd+=`<span class="sbadge" style="background:#fff0d0;color:var(--orange);border:1px solid var(--orange)">防謀略</span>`;

  $(`pbadge-${i}`).innerHTML=bd;

  const bt=p.businesses.map(bId=>{

   const b=BUSINESSES.find(x=>x.id===bId);

   const dmg=p.damagedBiz===bId;

   const lv=p.bizLevels[bId]||0;

   const lvBadge=lv>0?`<span style="font-size:.52rem;background:var(--blue);color:#fff;padding:0 4px;border-radius:3px;margin-left:2px">Lv${lv}</span>`:'';

   return `<span class="biz-tag${dmg?' dmg':''}">${b.icon}${b.name}${lvBadge}${dmg?'⚠️':''}</span>`;

  }).join('')||'<span style="font-size:.6rem;color:#aaa">尚無產業</span>';

  $(`pb-${i}`).innerHTML=bt;

  $(`pp-${i}`).classList.toggle('active-turn',i===G.curPlayer&&!cp.isAI);

  $(`pp-${i}`).classList.toggle('bankrupt',p.bankrupted);

 });

 updateTicker();

 renderLog();

}


function updateTicker(){

 const icons={stock:'📈',bond:'📜',reale:'🏗️',gold:'🥇',crypto:'₿',vc:'🦄'};

 const names={stock:'股市',bond:'債券',reale:'房市',gold:'黃金',crypto:'加密',vc:'創投'};

 $('market-ticker').innerHTML='📊 ' + Object.entries(marketTrends).map(([k,v])=>{

  const s=v>1?'▲▲':v>0?'▲':v<-1?'▼▼':v<0?'▼':'─';

  return `${icons[k]}${names[k]} ${s}`;

 }).join('&nbsp;|&nbsp;');

}


function addLog(msg,cls=''){G.log.push({msg,cls})}

function renderLog(){

 $('log-box').innerHTML=[...G.log].reverse().slice(0,28)

  .map(l=>`<div class="le ${l.cls}">${l.msg}</div>`).join('');

}

function lcls(i){return`lp${i}`}


// ═══════════════════════════════════════════════════

// EVENTS & INCOME

// ═══════════════════════════════════════════════════

function shiftTrends(){

 for(const k in marketTrends){

  marketTrends[k]+=Math.round((Math.random()-.5)*3);

  marketTrends[k]=Math.max(-3,Math.min(3,marketTrends[k]));

 }

}


function startTurn(){

 shiftTrends();

 // Scholar偶數回合免費情報

 G.players.forEach(p=>{

  if(p.scholarPreview&&G.turn%2===0&&!p.bankrupted){

   const preview=EVENTS[Math.floor(Math.random()*EVENTS.length)];

   G.nextEventPreview=preview;

   addLog(`📚 ${p.name}(儒商)情報:下回合事件預測「${preview.title}」`,lcls(p.idx));

  }

  // 重置武將威嚇狀態

  p.gExtortUsedThisTurn=false;

 });


 const ev = G.nextEventPreview || EVENTS[Math.floor(Math.random()*EVENTS.length)];

 G.nextEventPreview=null;

 G.currentEvent=ev; G.eventMod=ev.effect;

 const eb=$('event-box');

 eb.className=ev.danger?'danger':'';

 $('et').textContent=ev.title; $('ed').textContent=ev.desc;

 eb.style.display='block';

 if(ev.danger){eb.style.animation='shake .4s ease';setTimeout(()=>eb.style.animation='',500)}

 else eb.style.animation='popIn .3s ease';

 addLog(`📜 第${G.turn}回合:${ev.title}`,'lev');

 applyEventImmediate(ev);

 collectIncome();

 G.curPlayer=0;

 G.merchantThirdAction=false;

 G.actionsLeft=actionsForPlayer(0);

 updateUI();

 nextPlayerAction();

}


function actionsForPlayer(idx){

 return 2 + (G.players[idx].extraAction||0);

}


function applyEventImmediate(ev){

 G.players.forEach((p,i)=>{

  if(p.bankrupted) return;

  const cl=lcls(i);

  // Noble護盾:危險事件有30%機率被信譽屏障阻擋

  const shielded=p.nobleShield&&p.rep>=6&&ev.danger&&Math.random()<0.3;

  if(shielded){addLog(`🛡️ ${p.name}(世家)高信譽護盾抵擋了事件懲罰!`,cl);return;}

  switch(ev.effect){

   case 'subsidy':    p.money+=400; addLog(`🏛️ ${p.name} 獲補助$400`,cl); break;

   case 'media_scandal': if(p.rep<3&&!p.bribeActive){p.money=Math.max(0,p.money-500);addLog(`📰 ${p.name}信譽不足,損失$500`,'lwn')} break;

   case 'trade_open':  if(p.rep>=4){p.money+=600;addLog(`⚖️ ${p.name}信譽高,獲商機$600`,cl)} break;

   case 'tech_wave':   if(p.businesses.includes('tech')){p.money+=1000;addLog(`⚡ ${p.name}科技公司紅利$1000`,cl)} break;

   case 'tax_raid':   if(p.assets>8000){const l=Math.floor(p.assets*.1);p.money=Math.max(0,p.money-l);addLog(`🔍 ${p.name}被查稅,損失$${l}`,'lwn')} break;

   case 'labor_unrest': {const l=p.staff*80;if(l>0){p.money=Math.max(0,p.money-l);addLog(`🌧️ ${p.name}勞資糾紛,損失$${l}`,'lwn')}} break;

   case 'rate_up':    if(p.businesses.includes('finance')){const l=Math.floor(1200*.3);p.money=Math.max(0,p.money-l);addLog(`🏦 ${p.name}財務公司受升息衝擊-$${l}`,'lwn')} break;

  }

  p.bribeActive=false;

 });

}


function collectIncome(){

 const infMod=G.eventMod==='inflation'?1.3:1;

 G.players.forEach((p,i)=>{

  if(p.bankrupted) return;

  const cl=lcls(i);

  // Repay loans

  [...p.loans].forEach(loan=>{

   const repay=Math.min(p.money,loan.amount);

   p.money-=repay; p.debt=Math.max(0,p.debt-loan.amount);

   if(repay<loan.amount){p.rep=Math.max(1,p.rep-2);addLog(`💀 ${p.name}無力還款!信譽-2`,'lwn')}

   else addLog(`🏦 ${p.name} 還清借貸$${loan.amount}`,cl);

  });

  p.loans=[];

  if(p.frozen){p.frozen=false;addLog(`🔓 ${p.name}資金解凍`,cl);return;}


  // 被動收入(世家分潤$200 / 走私$150)

  if(p.passiveIncome>0){

   p.money+=p.passiveIncome;

   const label=p.charId==='noble'?'家族分潤':p.charId==='pirate'?'走私收入':'被動收入';

   addLog(`💰 ${p.name}(${label})+$${p.passiveIncome}`,cl);

  }


  let total=0;

  p.businesses.forEach(bId=>{

   const b=BUSINESSES.find(x=>x.id===bId);

   const lvl=p.bizLevels[bId]||0;

   const upgrades=BIZ_UPGRADES[bId]||[];

   let baseInc=b.income;

   for(let lv=0;lv<lvl;lv++) baseInc+=upgrades[lv].incomeAdd;

   if(p.luxuryBonus&&bId==='hotel') baseInc=Math.floor(baseInc*1.5);

   if(p.ftaBonus&&bId==='export')  baseInc=Math.floor(baseInc*1.4);

   let inc=baseInc;

   if(G.eventMod==='recession')                 inc=Math.floor(inc*.6);

   if(G.eventMod==='boom')                   inc=Math.floor(inc*1.5);

   if(G.eventMod==='innovation'&&(bId==='tech'||bId==='media')) inc=Math.floor(inc*1.6);

   if(G.eventMod==='fx_shock'&&bId==='export')         inc*=(p.fxHedge?1.5:2);

   if(G.eventMod==='rate_up'&&bId==='finance')         inc=Math.floor(inc*.7);

   if(p.hasMerger&&p.hasMerger.length>0)            inc=Math.floor(inc*1.2);

   if(p.monopoly)                        inc=Math.floor(inc*1.15);

   if(p.scandal)                        inc=Math.floor(inc*.75);

   if(p.damagedBiz===bId){

    if(p.hasPatent&&p.hasPatent.includes(bId)){

     addLog(`🛡️ ${p.name}的${b.name}專利防護,破壞無效!`,lcls(i));

    } else inc=0;

   }

   if(p.exportLic&&bId==='export'&&G.eventMod!=='recession') inc=Math.floor(inc*1.5);

   const effBonus=p.staff*150;

   inc+=Math.floor(effBonus/Math.max(1,p.businesses.length));

   total+=inc;

  });

  if(p.flagshipBonus) total+=p.flagshipBonus;

  if(p.intlIncome)  total+=p.intlIncome;

  if(p.chainBonus)  total+=p.chainBonus;

  const wages=Math.floor(p.staff*80*infMod);

  total-=wages;

  if(total>0){p.money+=total;addLog(`💼 ${p.name} 收入$${total}(扣工資後)`,cl)}

  else if(total<0){p.money=Math.max(0,p.money+total);addLog(`📉 ${p.name} 虧損$${Math.abs(total)}`,'lwn')}

  p.hasMerger=[]; p.monopoly=false; p.damagedBiz=null; p.mInterceptActive=false;

  p.assets=p.businesses.reduce((a,bId)=>{

   const b=BUSINESSES.find(x=>x.id===bId);

   const lv=p.bizLevels[bId]||0;

   return a+Math.floor(b.cost*.8*(1+lv*0.3));

  },0);

 });

 updateUI();

}


// ═══════════════════════════════════════════════════

// TURN FLOW

// ═══════════════════════════════════════════════════

function nextPlayerAction(){

 const p=G.players[G.curPlayer];

 updateUI();

 if(p.isAI){

  $('at').textContent=`🤖 ${p.name} 正在思考…`;

  $('ac').innerHTML=`<div class="ai-thinking">🤖 電腦思考中…</div>`;

  setTimeout(()=>aiTakeTurn(), 900);

 } else {

  showActionPhase();

 }

}


function useAction(){

 G.actionsLeft--;

 // 老商人第3次行動標記重置

 if(G.merchantThirdAction) G.merchantThirdAction=false;

 updateUI();

 if(G.actionsLeft<=0) setTimeout(()=>advancePlayer(),180);

 else {

  // 老商人第3次行動:標記

  const p=G.players[G.curPlayer];

  if(p.merchantExtraAction && G.actionsLeft===1){

   G.merchantThirdAction=true;

  }

  nextPlayerAction();

 }

}


function endPlayerTurn(){advancePlayer()}


function advancePlayer(){

 const total=G.playerCount;

 let next=(G.curPlayer+1)%total;

 let looped=0;

 while(G.players[next].bankrupted&&looped<total){next=(next+1)%total;looped++}

 if(looped>=total||next<=G.curPlayer){

  checkBankruptcy();

  G.turn++;

  if(G.turn>G.maxTurns){endGame();return}

  $('event-box').style.display='none';

  startTurn();

 } else {

  G.curPlayer=next;

  G.merchantThirdAction=false;

  G.actionsLeft=actionsForPlayer(next);

  nextPlayerAction();

 }

}


function checkBankruptcy(){

 G.players.forEach((p,i)=>{

  if(!p.bankrupted&&p.money<=0&&p.businesses.length===0){

   p.bankrupted=true; addLog(`💀 ${p.name} 資不抵債,宣告破產!`,'lwn');

   $(`pp-${i}`).classList.add('bankrupt');

  }

 });

 const alive=G.players.filter(p=>!p.bankrupted);

 if(alive.length===1){endGame();return}

}


// ═══════════════════════════════════════════════════

// ACTION PHASE (Human)

// ═══════════════════════════════════════════════════

function showActionPhase(){

 const p=G.players[G.curPlayer];

 const isThirdRestricted = G.merchantThirdAction;

 const actionLabel = isThirdRestricted

  ? `⚖️ ${p.name} — <span class="action-3rd">第3次行動(受限:僅投資/升級,有懲罰風險)</span>`

  : `⚖️ ${p.name} — 行動(剩餘 ${G.actionsLeft} 次)`;

 $('at').innerHTML=actionLabel;

 const hasUpgradable=p.businesses.some(id=>(p.bizLevels[id]||0)<3);

 const hasSpecial = (p.charId==='madame'&&p.mInterceptLeft>0) || p.charId==='general';

 let tabsHTML=`<div class="tabs">`;

 if(!isThirdRestricted){

  tabsHTML+=`<button class="tab active" onclick="showBizTab(this)">🏢 產業</button>`;

  tabsHTML+=`<button class="tab" onclick="showTacTab(this)">🗡️ 謀略</button>`;

 }

 tabsHTML+=`<button class="tab${isThirdRestricted?' active':''}" onclick="showUpgTab(this)">⬆️ 升級${hasUpgradable?' 🔴':''}</button>`;

 tabsHTML+=`<button class="tab" onclick="showInvTab(this)">💰 投資</button>`;

 if(hasSpecial&&!isThirdRestricted){

  tabsHTML+=`<button class="tab sp-tab" onclick="showSpTab(this)">⚡ 特技</button>`;

 }

 tabsHTML+=`<button class="skip-btn" onclick="endPlayerTurn()">跳過行動</button>`;

 tabsHTML+=`</div><div id="tc"></div>`;

 $('ac').innerHTML=tabsHTML;

 if(isThirdRestricted) showUpgContent();

 else showBizContent();

}


function setActiveTab(el){

 document.querySelectorAll('#ac .tab').forEach(t=>t.classList.remove('active'));

 el.classList.add('active');

}

function showBizTab(el){setActiveTab(el);showBizContent()}

function showUpgTab(el){setActiveTab(el);showUpgContent()}

function showInvTab(el){setActiveTab(el);showInvContent()}

function showTacTab(el){setActiveTab(el);showTacContent()}

function showSpTab(el){setActiveTab(el);showSpContent()}


function showBizContent(){

 const p=G.players[G.curPlayer];

 const costMod=G.eventMod==='inflation'?1.3:1;

 let h=`<div class="plabel">開設產業</div><div class="card-grid">`;

 h+=BUSINESSES.map(b=>{

  const owned=p.businesses.includes(b.id);

  const cost=Math.floor(b.cost*costMod);

  const canBuy=!owned&&p.money>=cost;

  return `<div class="card${owned?' owned-card':!canBuy?' disabled':''}" onclick="${!owned&&canBuy?`buyBiz('${b.id}')`:''}" >

   <div class="ci">${b.icon}</div><div class="ct">${b.name}</div>

   <div class="cd">${b.desc}</div>

   <div class="cc">投資$${cost.toLocaleString()} /需${b.staff}員工</div>

   <div class="cr">每回合$${b.income} /信譽+${b.rep}</div>

   <div class="cq">⚠️ 風險:${BIZ_RISKS[b.riskId].name}</div>

  </div>`;

 }).join('');

 h+=`</div><p class="tip">💡 擁有員工讓每回合收益提升,但工資是固定成本。</p>`;

 $('tc').innerHTML=h;

}


function showUpgContent(){

 const p=G.players[G.curPlayer];

 const costMod=G.eventMod==='inflation'?1.3:1;

 const isRestricted=G.merchantThirdAction;

 if(p.businesses.length===0){

  $('tc').innerHTML=`<div class="plabel">產業升級</div>${isRestricted?'<p style="font-size:.75rem;color:var(--red);padding:8px;background:rgba(176,48,32,.07);border-radius:6px;margin-bottom:8px">⚠️ 老商人第3次行動僅限投資或升級,請先開設產業或切換至投資頁籤。</p>':''}<p style="font-size:.8rem;color:#888;padding:16px 0">尚未開設任何產業。</p>`;

  return;

 }

 let h=`<div class="plabel">產業升級</div>

 ${isRestricted?'<p style="font-size:.7rem;color:var(--red);background:rgba(176,48,32,.07);padding:6px 10px;border-radius:6px;margin-bottom:8px">⚠️ 老商人受限行動:僅可投資或升級,有15%機率觸發懲罰。</p>':''}

 <div class="card-grid">`;

 p.businesses.forEach(bId=>{

  const b=BUSINESSES.find(x=>x.id===bId);

  const lvl=p.bizLevels[bId]||0;

  const upgrades=BIZ_UPGRADES[bId]||[];

  if(lvl>=3||upgrades.length===0){

   h+=`<div class="card owned-card" style="opacity:.7;pointer-events:none">

    <div class="ci">${b.icon}</div><div class="ct">${b.name}</div>

    <div style="font-size:.7rem;color:var(--green);font-weight:700;margin-top:6px">✅ 已滿級 Lv.3</div>

   </div>`;

  } else {

   const next=upgrades[lvl];

   const cost=Math.floor(next.cost*costMod);

   const canUpg=p.money>=cost;

   h+=`<div class="card${!canUpg?' disabled':''}" onclick="${canUpg?`upgradeBiz('${bId}')`:''}" >

    <div style="display:flex;justify-content:space-between;align-items:start">

     <div class="ci">${b.icon}</div>

     <div style="font-size:.62rem;background:var(--blue);color:#fff;padding:1px 6px;border-radius:8px;font-weight:700">Lv.${lvl}→${lvl+1}</div>

    </div>

    <div class="ct">${b.name}</div>

    <div class="cd">⬆️ ${next.perk}</div>

    <div class="cr">收入 +$${next.incomeAdd}/回合</div>

    ${next.staffAdd>0?`<div class="cr">員工 +${next.staffAdd}</div>`:''}

    ${next.staffAdd<0?`<div class="cr" style="color:var(--purple)">自動化,節省${Math.abs(next.staffAdd)}工資</div>`:''}

    <div class="cc">升級費用 $${cost.toLocaleString()}</div>

   </div>`;

  }

 });

 h+=`</div>`;

 $('tc').innerHTML=h;

}


function showInvContent(){

 const p=G.players[G.curPlayer];

 const isRestricted=G.merchantThirdAction;

 let h=`<div class="plabel">投資市場</div>

 ${isRestricted?'<p style="font-size:.7rem;color:var(--red);background:rgba(176,48,32,.07);padding:6px 10px;border-radius:6px;margin-bottom:8px">⚠️ 老商人受限行動:投資有15%機率觸發懲罰(損失$300)。</p>':''}

 <table class="mtable"><thead><tr><th>資產</th><th>投入</th><th>預期範圍</th><th>趨勢</th><th>操作</th></tr></thead><tbody>`;

 INVESTMENTS.forEach(inv=>{

  const t=marketTrends[inv.id]||0;

  const ts=t>1?`<span class="tu">▲▲${t}</span>`:t>0?`<span class="tu">▲${t}</span>`:t<-1?`<span class="td">▼▼${Math.abs(t)}</span>`:t<0?`<span class="td">▼${Math.abs(t)}</span>`:`<span class="tf">─</span>`;

  const eff=inv.base+t*.08;

  const mn=Math.floor(-inv.cost*inv.vol*.5);

  const mx=Math.floor(inv.cost*eff*2*(1+p.investBonus));

  const cant=p.money<inv.cost;

  h+=`<tr class="${cant?'cant':''}" onclick="${!cant?`doInvest('${inv.id}')`:''}" title="${inv.desc}">

   <td>${inv.icon} ${inv.name}</td><td>$${inv.cost}</td>

   <td>${mn<0?'$'+mn:'$0'} ~ +$${mx}</td><td>${ts}</td>

   <td><button class="btn btn-gold" style="padding:2px 9px;font-size:.68rem" ${cant?'disabled':''} onclick="event.stopPropagation();doInvest('${inv.id}')">投入</button></td>

  </tr>`;

 });

 h+=`</tbody></table>`;

 if(p.investBonus>0) h+=`<p class="tip">📚 你的投資回報加成:+${Math.round(p.investBonus*100)}%</p>`;

 $('tc').innerHTML=h;

}


function showTacContent(){

 const p=G.players[G.curPlayer];

 const opponents=G.players.filter((_,i)=>i!==G.curPlayer&&!G.players[i].bankrupted);

 let h=`<div class="plabel">謀略行動</div><div class="card-grid">`;

 h+=TACTICS.map(t=>{

  const cost=Math.floor(t.cost*(1-p.tacticDiscount));

  let canDo=p.money>=cost&&p.rep>=t.repReq&&p.staff>=t.staffReq;

  if(t.id==='loan')   canDo=p.loans.length===0&&p.rep>=2;

  if(t.id==='highloan') canDo=p.loans.length===0;

  if(t.id==='fire_sale')canDo=p.businesses.length>0;

  if(t.id==='sabotage') canDo=p.money>=cost&&opponents.some(o=>o.businesses.length>0);

  if(t.id==='poach')  canDo=p.money>=cost&&opponents.some(o=>o.staff>0);

  if(t.id==='monopoly') canDo=p.money>=cost&&p.rep>=4&&p.staff>=5;

  if(t.id==='merger')  canDo=p.money>=cost&&p.rep>=2&&opponents.length>0;

  if(t.id==='smear')  canDo=p.money>=cost&&opponents.length>0;

  const critNote=p.tacticCrit>0?`<div class="cop">⚡ ${Math.round(p.tacticCrit*100)}%暴擊機率</div>`:'';

  const discNote=p.tacticDiscount>0&&cost<(t.cost||0)?`<div class="cr">折扣後$${cost}</div>`:'';

  return `<div class="card${t.danger?' dng':''}${!canDo?' disabled':''}" onclick="${canDo?`doTactic('${t.id}')`:''}" >

   <div class="ci">${t.icon}</div><div class="ct">${t.name}</div>

   <div class="cd">${t.desc}</div>

   ${cost>0?`<div class="cc">費用$${cost}</div>`:''}

   ${discNote}${critNote}

   ${t.repReq>0?`<div class="cq">需信譽≥${t.repReq}</div>`:''}

  </div>`;

 }).join('');

 h+=`</div>`;

 $('tc').innerHTML=h;

}


function showSpContent(){

 const p=G.players[G.curPlayer];

 const opponents=G.players.filter((_,i)=>i!==G.curPlayer&&!G.players[i].bankrupted);

 let h=`<div class="plabel plabel-sp">⚡ 角色特技</div><div class="card-grid">`;

 if(p.charId==='madame'){

  const ab=SPECIAL_ABILITIES.madame_intercept;

  const canDo=p.mInterceptLeft>0&&p.money>=ab.cost&&opponents.length>0;

  h+=`<div class="card sp-card${!canDo?' disabled':''}" onclick="${canDo?`doSpecial('madame_intercept')`:''}" >

   <div class="ci">${ab.icon}</div><div class="ct">${ab.name}</div>

   <div class="cd">${ab.desc}</div>

   <div class="cc">費用$${ab.cost}</div>

   <div class="cop">剩餘次數:${p.mInterceptLeft}次</div>

  </div>`;

 }

 if(p.charId==='general'){

  const ab=SPECIAL_ABILITIES.general_extort;

  const validTargets=opponents.filter(o=>o.rep<p.rep);

  const canDo=!p.gExtortUsedThisTurn&&validTargets.length>0;

  h+=`<div class="card sp-card${!canDo?' disabled':''}" onclick="${canDo?`doSpecial('general_extort')`:''}" >

   <div class="ci">${ab.icon}</div><div class="ct">${ab.name}</div>

   <div class="cd">${ab.desc}</div>

   <div class="cop">目標需信譽低於你(你目前⭐${p.rep})</div>

   ${p.gExtortUsedThisTurn?'<div class="cc">本回合已使用</div>':''}

   ${validTargets.length===0?'<div class="cc">無符合條件目標</div>':''}

  </div>`;

 }

 h+=`</div><p class="tip">⚡ 角色特技是額外行動,不消耗主行動次數。</p>`;

 $('tc').innerHTML=h;

}


// ═══════════════════════════════════════════════════

// ACTIONS

// ═══════════════════════════════════════════════════

function buyBiz(id){

 const p=G.players[G.curPlayer];

 const b=BUSINESSES.find(x=>x.id===id);

 const costMod=G.eventMod==='inflation'?1.3:1;

 const cost=Math.floor(b.cost*costMod);

 if(p.money<cost||p.businesses.includes(id)) return;

 p.money-=cost; p.businesses.push(id);

 p.rep=Math.min(p.repCap,p.rep+b.rep); p.staff+=b.staff;

 p.assets+=Math.floor(cost*.8);

 addLog(`🏢 ${p.name} 開設「${b.name}」花費$${cost}`,lcls(G.curPlayer));

 if(Math.random()<0.25&&!p.bribeActive){

  const risk=BIZ_RISKS[b.riskId];

  const msg=risk.effect(p,b);

  addLog(`⚠️ 開業風險!${msg}`,'lwn');

  updateUI();

 }

 useAction();

}


function upgradeBiz(id){

 const p=G.players[G.curPlayer];

 const b=BUSINESSES.find(x=>x.id===id);

 const lvl=p.bizLevels[id]||0;

 if(lvl>=3) return;

 const upgrades=BIZ_UPGRADES[id];

 const next=upgrades[lvl];

 const costMod=G.eventMod==='inflation'?1.3:1;

 const cost=Math.floor(next.cost*costMod);

 if(p.money<cost) return;

 p.money-=cost;

 p.bizLevels[id]=(lvl+1);

 if(!p.bizPerks[id]) p.bizPerks[id]=[];

 p.bizPerks[id].push(next.perk.split(',')[0]);

 if(next.staffAdd) p.staff=Math.max(0,p.staff+next.staffAdd);

 const sp=next.special;

 if(sp==='rep+1')  p.rep=Math.min(p.repCap,p.rep+1);

 if(sp==='rep+2')  p.rep=Math.min(p.repCap,p.rep+2);

 if(sp==='inv_bonus')p.investBonus=(p.investBonus||0)+0.1;

 if(sp==='patent'){p.hasPatent=p.hasPatent||[];p.hasPatent.push(id);}

 if(sp==='ipo')   {p.money+=2000; addLog(`🚀 ${p.name}的${b.name} IPO!立即套現$2000`,lcls(G.curPlayer));}

 if(sp==='tac_disc') p.tacticDiscount=Math.min(0.4,(p.tacticDiscount||0)+0.1);

 if(sp==='free_smear')p.freeSmear=(p.freeSmear||0)+1;

 if(sp==='fund')   p.investBonus=(p.investBonus||0)+0.15;

 if(sp==='auto')   addLog(`🤖 ${p.name}的${b.name}自動化!`,lcls(G.curPlayer));

 if(sp==='flagship') p.flagshipBonus=(p.flagshipBonus||0)+300;

 if(sp==='intl')   p.intlIncome=(p.intlIncome||0)+500;

 if(sp==='luxury')  p.luxuryBonus=true;

 if(sp==='chain')  p.chainBonus=(p.chainBonus||0)+200;

 if(sp==='fta')   p.ftaBonus=true;

 if(sp==='fx_hedge') p.fxHedge=true;

 if(sp==='export_lic')p.exportLic=true;

 p.assets=p.businesses.reduce((a,bId)=>{

  const bz=BUSINESSES.find(x=>x.id===bId);

  const lv=p.bizLevels[bId]||0;

  return a+Math.floor(bz.cost*.8*(1+lv*0.3));

 },0);

 addLog(`⬆️ ${p.name} 升級「${b.name}」至 Lv.${p.bizLevels[id]}:${next.perk}`,lcls(G.curPlayer));

 // 老商人受限行動懲罰

 if(G.merchantThirdAction&&Math.random()<0.15){

  const penalty=300;

  p.money=Math.max(0,p.money-penalty);

  addLog(`⚠️ 老商人第3次行動操之過急,損失$${penalty}!`,'lwn');

 }

 useAction();

}


function doInvest(id){

 const p=G.players[G.curPlayer];

 const inv=INVESTMENTS.find(x=>x.id===id);

 if(p.money<inv.cost) return;

 // 老商人受限行動懲罰

 if(G.merchantThirdAction&&Math.random()<0.15){

  const penalty=300;

  p.money=Math.max(0,p.money-penalty);

  addLog(`⚠️ 老商人第3次行動消息走漏,損失$${penalty}!`,'lwn');

  useAction(); return;

 }

 p.money-=inv.cost;

 const t=marketTrends[id]||0;

 let ret;

 if(G.eventMod==='crash'){

  ret=-Math.floor(inv.cost*inv.vol*.5);

 } else if(G.eventMod==='invest_boom'&&(id==='stock'||id==='crypto')){

  ret=Math.floor(inv.cost*inv.base*3);

 } else {

  const eff=inv.base+t*.08;

  ret=Math.floor(inv.cost*eff*(1+(Math.random()-.4)*inv.vol));

 }

 const bonus=p.investBonus||0;

 ret=Math.floor(ret*(1+bonus));

 // Pirate暴擊

 let critMsg='';

 if(p.tacticCrit>0&&Math.random()<p.tacticCrit&&ret>0){

  ret=Math.floor(ret*1.5);

  critMsg=' ⚡暴擊!';

 }

 const final=Math.max(-inv.cost,ret);

 p.money+=inv.cost+final;

 if(final>0) p.assets+=final; else p.assets=Math.max(0,p.assets+final);

 addLog(`${final>=0?'📈':'📉'} ${p.name} 投資${inv.name},${final>=0?'獲利':'虧損'}$${Math.abs(final)}${critMsg}`,lcls(G.curPlayer));

 useAction();

}


function doTactic(id){

 const p=G.players[G.curPlayer];

 const cl=lcls(G.curPlayer);

 const tac=TACTICS.find(t=>t.id===id);

 const cost=Math.floor((tac?.cost||0)*(1-p.tacticDiscount));

 const opponents=G.players.filter((_,i)=>i!==G.curPlayer&&!G.players[i].bankrupted);

 if((id==='sabotage'||id==='poach'||id==='smear'||id==='merger')&&opponents.length>1){

  const opts=opponents.map(o=>`<option value="${o.idx}">${o.avatar} ${o.name}</option>`).join('');

  showModal('選擇目標',`<select id="tgt-sel" style="padding:4px 8px;font-size:.82rem;margin-top:6px">${opts}</select>`,

   '確認',()=>{

    const tIdx=parseInt(document.getElementById('tgt-sel').value);

    executeTactic(id,p,G.players[tIdx],cost,cl);

   });

  return;

 }

 executeTactic(id,p,opponents[0],cost,cl);

}


function executeTactic(id,p,opp,cost,cl){

 // 女強人斡旋:目標如果設有防謀略則失效

 if(opp&&opp.mInterceptActive&&['sabotage','smear','poach'].includes(id)){

  addLog(`🛡️ ${opp.name}的斡旋談判生效!${p.name}的「${id}」行動被阻止!`,'lsp');

  opp.mInterceptActive=false; useAction(); return;

 }

 // Pirate謀略暴擊判定(在switch之前)

 const pirateCrit = p.tacticCrit>0&&Math.random()<p.tacticCrit;


 switch(id){

  case 'poach':{

   if(!opp||opp.staff===0){addLog(`🕵️ ${p.name}找不到可挖角的員工`,'lwn');useAction();return}

   p.money-=cost; let stolen=Math.min(2,opp.staff);

   if(pirateCrit){stolen=Math.min(3,opp.staff+1);addLog(`⚡ 暴擊!`,'lsp')}

   opp.staff=Math.max(0,opp.staff-stolen); p.staff+=stolen;

   addLog(`🕵️ ${p.name} 挖角成功!從${opp.name}搶走${stolen}名員工`,cl);break;}

  case 'smear':{

   if(!opp){useAction();return}

   const eff=1+(p.allyBonus||0);

   p.money-=cost;

   let repLoss=Math.round(2*eff);

   if(pirateCrit){repLoss=Math.round(repLoss*1.5);addLog(`⚡ 暴擊!`,'lsp')}

   opp.rep=Math.max(1,opp.rep-repLoss); p.rep=Math.max(1,p.rep-1); opp.scandal=true;

   addLog(`🗞️ ${p.name} 黑公關!${opp.name}信譽-${repLoss},陷入醜聞`,cl);break;}

  case 'pr':{

   const eff2=1+(p.allyBonus||0);

   p.money-=cost; p.rep=Math.min(p.repCap,p.rep+Math.round(2*eff2)); p.scandal=false;

   addLog(`📢 ${p.name} 公關攻勢,信譽+${Math.round(2*eff2)},清除醜聞`,cl);break;}

  case 'bribe':{

   p.money-=cost; p.bribeActive=true;

   addLog(`💼 ${p.name} 疏通關係,下回合免疫懲罰事件`,cl);break;}

  case 'loan':{

   if(p.loans.length>0){addLog(`🏦 ${p.name}已有借貸`,'lwn');return}

   p.money+=2000; p.rep=Math.max(1,p.rep-1); p.loans.push({amount:2400}); p.debt+=2400;

   addLog(`🏦 ${p.name} 借貸$2000,下回合還$2400,信譽-1`,cl);break;}

  case 'highloan':{

   if(p.loans.length>0){addLog(`💳 ${p.name}已有借貸`,'lwn');return}

   p.money+=3000; p.rep=Math.max(1,p.rep-2); p.loans.push({amount:4000}); p.debt+=4000;

   addLog(`💳 ${p.name} 高利貸$3000,下回合還$4000!信譽-2`,cl);break;}

  case 'sabotage':{

   if(!opp||opp.businesses.length===0){addLog(`🔧 ${p.name}找不到可破壞的產業`,'lwn');useAction();return}

   p.money-=cost;

   const effBonus=p.sabBonus||0;

   const targets=opp.businesses;

   const tgt=targets[Math.floor(Math.random()*targets.length)];

   opp.damagedBiz=tgt;

   const b=BUSINESSES.find(x=>x.id===tgt);

   if(effBonus>0&&Math.random()<effBonus){

    const extraLoss=Math.floor(b.income*(pirateCrit?0.75:0.5));

    opp.money=Math.max(0,opp.money-extraLoss);

    addLog(`🔧 ${p.name}${pirateCrit?' ⚡暴擊':''}重創「${b.name}」!${opp.name}停業+損失$${extraLoss}`,cl);

   } else {

    addLog(`🔧 ${p.name} 破壞${opp.name}的「${b.name}」${pirateCrit?' ⚡暴擊加深損害':''}`,cl);

   }

   break;}

  case 'insider':{

   p.money-=cost; G.nextEventPreview=EVENTS[Math.floor(Math.random()*EVENTS.length)];

   addLog(`🔮 ${p.name} 獲得內線消息!下回合事件:${G.nextEventPreview.title}`,cl);break;}

  case 'recruit':{

   p.money-=cost; p.staff+=3;

   addLog(`👥 ${p.name} 大規模招募,員工+3`,cl);break;}

  case 'monopoly':{

   if(p.rep<4||p.staff<5){addLog(`🎯 ${p.name}條件不足`,'lwn');return}

   p.money-=cost; p.monopoly=true; p.rep=Math.max(1,p.rep-1);

   addLog(`🎯 ${p.name} 壟斷定價,本回合收入+15%,信譽-1`,cl);break;}

  case 'merger':{

   if(!opp){useAction();return}

   const allyEff=1+(p.allyBonus||0);

   if(opp.isAI){

    const aiAccept=calcScore(opp)<calcScore(p)*1.2;

    if(aiAccept){

     p.money-=cost; p.hasMerger.push(opp.idx); opp.hasMerger.push(p.idx);

     addLog(`🤝 ${opp.name}(AI)接受聯盟!雙方收入+${Math.round(20*allyEff)}%`,'lsy');

     useAction(); return;

    } else {addLog(`🤝 ${opp.name}(AI)拒絕聯盟`,'lai'); useAction(); return;}

   }

   showModal(`策略聯盟提議`,`${p.name} 提出策略聯盟!<br>雙方本回合收入各+${Math.round(20*allyEff)}%。<br><br>${opp.name} 是否接受?`,

    '接受',()=>{

     p.money-=cost; p.hasMerger.push(opp.idx); opp.hasMerger.push(p.idx);

     addLog(`🤝 策略聯盟達成!雙方收入+${Math.round(20*allyEff)}%`,'lsy');

     useAction();

    },'拒絕');

   return;}

  case 'fire_sale':{

   if(p.businesses.length===0){useAction();return}

   const opts=p.businesses.map(bId=>{

    const b=BUSINESSES.find(x=>x.id===bId);

    const lv=p.bizLevels[bId]||0;

    const val=Math.floor(b.cost*(1+lv*0.3)*.6);

    return`<option value="${bId}">${b.icon}${b.name} Lv${lv}(回收$${val})</option>`;

   }).join('');

   showModal('甩賣產業',`選擇要急售的產業:<br><select id="ss" style="margin-top:8px;padding:4px 8px;font-size:.82rem">${opts}</select>`,

    '確認出售',()=>{

     const sel=document.getElementById('ss').value;

     const b=BUSINESSES.find(x=>x.id===sel);

     const lv=p.bizLevels[sel]||0;

     const cash=Math.floor(b.cost*(1+lv*0.3)*.6);

     p.businesses=p.businesses.filter(x=>x!==sel);

     delete p.bizLevels[sel]; delete p.bizPerks[sel];

     p.money+=cash; p.rep=Math.max(1,p.rep-1); p.staff=Math.max(0,p.staff-b.staff);

     p.assets=p.businesses.reduce((a,bId)=>{const bz=BUSINESSES.find(x=>x.id===bId);const lv2=p.bizLevels[bId]||0;return a+Math.floor(bz.cost*.8*(1+lv2*0.3));},0);

     addLog(`🔥 ${p.name} 甩賣「${b.name}(Lv${lv})」,回收$${cash},信譽-1`,cl);

     useAction();

    },'取消');

   return;}

 }

 useAction();

}


// ═══════════════════════════════════════════════════

// SPECIAL ABILITIES (Human)

// ═══════════════════════════════════════════════════

function doSpecial(abilityId){

 const p=G.players[G.curPlayer];

 const cl=lcls(G.curPlayer);

 const opponents=G.players.filter((_,i)=>i!==G.curPlayer&&!G.players[i].bankrupted);


 if(abilityId==='madame_intercept'){

  if(p.mInterceptLeft<=0||p.money<300){return}

  if(opponents.length>1){

   const opts=opponents.map(o=>`<option value="${o.idx}">${o.avatar} ${o.name}</option>`).join('');

   showModal('選擇斡旋目標',`花費$300,阻止哪位對手下一個謀略行動?<br><select id="tgt-sel" style="padding:4px 8px;font-size:.82rem;margin-top:6px">${opts}</select>`,

    '確認',()=>{

     const tIdx=parseInt(document.getElementById('tgt-sel').value);

     applyIntercept(p,G.players[tIdx],cl);

    });

   return;

  }

  applyIntercept(p,opponents[0],cl);

 }


 if(abilityId==='general_extort'){

  const validTargets=opponents.filter(o=>o.rep<p.rep);

  if(validTargets.length===0||p.gExtortUsedThisTurn) return;

  if(validTargets.length>1){

   const opts=validTargets.map(o=>`<option value="${o.idx}">${o.avatar}${o.name} ⭐${o.rep}</option>`).join('');

   showModal('選擇威嚇目標',`向信譽比你低的對手收取$400保護費:<br><select id="tgt-sel" style="padding:4px 8px;margin-top:6px">${opts}</select>`,

    '強行索費',()=>{

     const tIdx=parseInt(document.getElementById('tgt-sel').value);

     applyExtort(p,G.players[tIdx],cl);

    });

   return;

  }

  applyExtort(p,validTargets[0],cl);

 }

}


function applyIntercept(p,opp,cl){

 p.money-=300; p.mInterceptLeft--;

 opp.mInterceptActive=true;

 addLog(`🛡️ ${p.name} 斡旋談判,${opp.name}下次謀略行動將被阻止!(剩${p.mInterceptLeft}次)`,cl);

 // 特技不消耗主行動次數

 updateUI();

 showActionPhase();

}


function applyExtort(p,opp,cl){

 const amount=400;

 const paid=Math.min(opp.money,amount);

 opp.money-=paid;

 p.money+=paid;

 p.gExtortUsedThisTurn=true;

 if(paid<amount){

  opp.rep=Math.max(1,opp.rep-1);

  addLog(`⚔️ ${p.name} 威嚇${opp.name}!對方資金不足,付了$${paid}並信譽-1`,'lsp');

 } else {

  addLog(`⚔️ ${p.name} 向${opp.name}索取保護費$${amount}!`,'lsp');

 }

 updateUI();

 showActionPhase();

}


// ═══════════════════════════════════════════════════

// AI LOGIC

// ═══════════════════════════════════════════════════

function aiTakeTurn(){

 const p=G.players[G.curPlayer];

 if(p.bankrupted){advancePlayer();return}

 const opponents=G.players.filter((_,i)=>i!==G.curPlayer&&!G.players[i].bankrupted);

 const opp=opponents.reduce((best,o)=>calcScore(o)>calcScore(best)?o:best, opponents[0]||G.players[0]);

 const myScore=calcScore(p);

 const oppScore=opp?calcScore(opp):0;

 const cl='lai';

 let acted=false;

 const costMod=G.eventMod==='inflation'?1.3:1;

 const isThirdRestricted=G.merchantThirdAction;


 // AI特技:女強人斡旋

 if(!acted&&p.charId==='madame'&&p.mInterceptLeft>0&&p.money>=300&&opp&&oppScore>myScore*1.2){

  p.money-=300; p.mInterceptLeft--; opp.mInterceptActive=true;

  addLog(`🛡️ [AI] ${p.name} 斡旋談判防禦${opp.name}`,cl); acted=true;

 }

 // AI特技:武將威嚇

 if(!acted&&p.charId==='general'&&!p.gExtortUsedThisTurn&&opp&&opp.rep<p.rep){

  const paid=Math.min(opp.money,400); opp.money-=paid; p.money+=paid; p.gExtortUsedThisTurn=true;

  if(paid<400) opp.rep=Math.max(1,opp.rep-1);

  addLog(`⚔️ [AI] ${p.name} 威嚇索費$${paid}`,cl);

 }


 // 受限行動(老商人第3次)只能投資或升級

 if(isThirdRestricted){

  // 嘗試升級

  if(p.businesses.length>0){

   const upgOpts=p.businesses

    .filter(id=>(p.bizLevels[id]||0)<3)

    .map(id=>{const lvl=p.bizLevels[id]||0;const upg=BIZ_UPGRADES[id][lvl];const cost=Math.floor(upg.cost*costMod);return {id,cost,roi:upg.incomeAdd/cost};})

    .filter(u=>p.money>=u.cost).sort((a,b)=>b.roi-a.roi);

   if(upgOpts.length>0){upgradeBiz(upgOpts[0].id);return;}

  }

  // 投資

  const bestInv=INVESTMENTS.filter(inv=>p.money>=inv.cost)

   .sort((a,b)=>(marketTrends[b.id]||0)-(marketTrends[a.id]||0));

  if(bestInv.length>0){

   doInvest(bestInv[0].id); return;

  }

  addLog(`⏭️ [AI] ${p.name} 第3次受限行動無法執行,略過`,cl);

  useAction(); return;

 }


 if(!acted&&p.money<200&&p.businesses.length>0&&p.loans.length===0){

  const bId=p.businesses[0];const b=BUSINESSES.find(x=>x.id===bId);const cash=Math.floor(b.cost*.6);

  p.businesses=p.businesses.filter(x=>x!==bId);p.money+=cash;p.rep=Math.max(1,p.rep-1);p.staff=Math.max(0,p.staff-b.staff);p.assets=Math.max(0,p.assets-Math.floor(b.cost*.8));

  addLog(`🔥 [AI] ${p.name} 甩賣急救`,cl);acted=true;

 }

 if(!acted&&p.loans.length===0&&myScore<oppScore*.6&&p.money<1000){

  p.money+=3000;p.rep=Math.max(1,p.rep-2);p.loans.push({amount:4000});p.debt+=4000;

  addLog(`💳 [AI] ${p.name} 高利貸擴張`,cl);acted=true;

 }

 if(!acted&&p.businesses.length>0){

  const upgOpts=p.businesses

   .filter(id=>(p.bizLevels[id]||0)<3)

   .map(id=>{const lvl=p.bizLevels[id]||0;const upg=BIZ_UPGRADES[id][lvl];const cost=Math.floor(upg.cost*costMod);return{id,cost,roi:upg.incomeAdd/cost};})

   .filter(u=>p.money>=u.cost).sort((a,b)=>b.roi-a.roi);

  if(upgOpts.length>0&&upgOpts[0].roi>0.15){upgradeBiz(upgOpts[0].id);return;}

 }

 if(!acted){

  const affordable=BUSINESSES.filter(b=>!p.businesses.includes(b.id)&&p.money>=Math.floor(b.cost*costMod)).sort((a,b)=>b.income-a.income);

  if(affordable.length>0){

   const b=affordable[0];const cost=Math.floor(b.cost*costMod);

   p.money-=cost;p.businesses.push(b.id);p.rep=Math.min(p.repCap,p.rep+b.rep);p.staff+=b.staff;p.assets+=Math.floor(cost*.8);

   addLog(`🏢 [AI] ${p.name} 開設「${b.name}」`,cl);

   if(Math.random()<0.25&&!p.bribeActive){const risk=BIZ_RISKS[b.riskId];risk.effect(p,b)}

   acted=true;

  }

 }

 if(!acted){

  const bestInv=INVESTMENTS.filter(inv=>p.money>=inv.cost).sort((a,b)=>(marketTrends[b.id]||0)-(marketTrends[a.id]||0));

  if(bestInv.length>0&&(marketTrends[bestInv[0].id]||0)>=0){

   const inv=bestInv[0];const t=marketTrends[inv.id]||0;const eff=inv.base+t*.08;

   let ret=Math.floor(inv.cost*eff*(1+(Math.random()-.4)*inv.vol));

   if(p.tacticCrit>0&&Math.random()<p.tacticCrit&&ret>0) ret=Math.floor(ret*1.5);

   const final=Math.max(-inv.cost,ret);p.money+=final;if(final>0)p.assets+=final;

   addLog(`${final>=0?'📈':'📉'} [AI] ${p.name} 投資${inv.name},${final>=0?'獲利':'虧損'}$${Math.abs(final)}`,cl);acted=true;

  }

 }

 if(!acted&&opp&&opp.businesses.length>0&&oppScore>myScore*1.3&&p.money>=1000){

  p.money-=1000;opp.damagedBiz=opp.businesses[Math.floor(Math.random()*opp.businesses.length)];

  const b=BUSINESSES.find(x=>x.id===opp.damagedBiz);

  if(p.sabBonus>0){const loss=Math.floor(b.income*.5);opp.money=Math.max(0,opp.money-loss);}

  addLog(`🔧 [AI] ${p.name} 破壞${opp.name}的「${b.name}」`,cl);acted=true;

 }

 if(!acted&&opp&&opp.rep>=5&&p.money>=700){

  const tCost=Math.floor(700*(1-p.tacticDiscount));

  p.money-=tCost;opp.rep=Math.max(1,opp.rep-2);p.rep=Math.max(1,p.rep-1);opp.scandal=true;

  addLog(`🗞️ [AI] ${p.name} 黑公關打壓${opp.name}`,cl);acted=true;

 }

 if(!acted&&p.rep<=2&&p.money>=600){

  p.money-=600;p.rep=Math.min(p.repCap,p.rep+2);p.scandal=false;

  addLog(`📢 [AI] ${p.name} 公關攻勢`,cl);acted=true;

 }

 if(!acted&&p.businesses.length>0&&p.staff<4&&p.money>=400){

  p.money-=400;p.staff+=3;addLog(`👥 [AI] ${p.name} 招募員工`,cl);acted=true;

 }

 if(!acted&&p.loans.length===0&&p.rep>=2&&p.money<1200&&p.businesses.length<3){

  p.money+=2000;p.rep=Math.max(1,p.rep-1);p.loans.push({amount:2400});p.debt+=2400;

  addLog(`🏦 [AI] ${p.name} 借貸擴充`,cl);acted=true;

 }

 if(!acted&&G.currentEvent?.danger&&p.money>=900){

  p.money-=900;p.bribeActive=true;addLog(`💼 [AI] ${p.name} 疏通自保`,cl);acted=true;

 }

 if(!acted) addLog(`⏭️ [AI] ${p.name} 略過行動`,cl);


 updateUI();

 setTimeout(()=>useAction(), 600);

}


// ═══════════════════════════════════════════════════

// MODAL

// ═══════════════════════════════════════════════════

function showModal(title,body,yesLabel,yesCb,noLabel){

 const div=document.createElement('div');

 div.className='modal-ov';

 div.innerHTML=`<div class="modal"><h3 class="serif">${title}</h3><p>${body}</p><div class="modal-btns"><button class="btn btn-ink" id="mno">${noLabel||'取消'}</button><button class="btn btn-gold" id="myes">${yesLabel||'確認'}</button></div></div>`;

 document.body.appendChild(div);

 div.querySelector('#myes').onclick=()=>{document.body.removeChild(div);yesCb()};

 div.querySelector('#mno').onclick=()=>{document.body.removeChild(div)};

}


// ═══════════════════════════════════════════════════

// SCORING & END

// ═══════════════════════════════════════════════════

function calcScore(p){

 if(p.bankrupted) return 0;

 return Math.max(0,p.money+p.assets*1.8+p.rep*250-p.debt*1.5);

}


function endGame(){

 const ranked=[...G.players].sort((a,b)=>calcScore(b)-calcScore(a));

 const sg=$('score-grid');

 sg.className='score-grid '+(G.playerCount===2?'sg2':'sg3');

 sg.innerHTML=ranked.map((p,rank)=>{

  const s=calcScore(p);

  return `<div class="score-card${rank===0?' winner-card':''}">

   ${rank===0?'<div class="sc-rank">🏆 冠軍</div>':rank===1?'<div class="sc-rank">第2名</div>':'<div class="sc-rank">第3名</div>'}

   <div style="font-size:1.5rem;margin-bottom:3px">${p.avatar}</div>

   <div style="font-family:Noto Serif TC,serif;font-size:.88rem;font-weight:900;color:${p.color}">${p.name}</div>

   <div class="sc-total" style="color:${p.color}">${s.toLocaleString()}</div>

   <div class="sc-break">

    資金 $${p.money.toLocaleString()}<br>

    資產 $${p.assets.toLocaleString()} ×1.8<br>

    信譽 ${p.rep} ×250<br>

    債務 -$${p.debt.toLocaleString()} ×1.5<br>

    ${p.bankrupted?'<span style="color:var(--red)">已破產</span>':''}

   </div>

  </div>`;

 }).join('');

 const winner=ranked[0];

 $('winner-msg').textContent=winner.bankrupted?'⚖️ 無人倖存!':`${winner.avatar} ${winner.name} 稱霸商海!`;

 $('game-screen').classList.add('hide');

 $('result-screen').style.display='block';

 $('result-screen').classList.add('show');

}


// ═══════════════════════════════════════════════════

// INIT

// ═══════════════════════════════════════════════════

renderPlayerSetup();

</script>

</body>

</html>

Comments (0)

想要討論這個主題?

加入我們的社群討論區,與其他讀者深入交流