
商海浮沉 亂世版 v3
<!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> | 行動者:<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(' | ');
}
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>