你是大鴿的「好友」。每一季,都可能讓他的人生往不同方向偏移。
虛構化社群人生模擬
角色採暱稱與虛構化設定,不使用真實地址、健康資料或其他敏感個資。遊戲核心是社群關係、誤會、競爭與人生起伏;不鼓勵對現實人物進行騷擾、肉搜或跟拍。

建立你的假好友

const LIFE_CATS={ "感情":[ ["在朋友聚會認識一名聊得來的人","大鴿回家後一直提到對方,顯然很在意。"], ["收到一則曖昧訊息","他反覆研究那句話到底是什麼意思。"], ["第一次正式約會","他提前幾天就開始緊張。"], ["感情突然冷掉","原本熱絡的互動突然變少。"], ["被介紹相親","他嘴上嫌麻煩,實際上還是去了。"], ["考慮告白","他問你『現在講會不會太早?』"], ["交往紀念日到了","他差點忘記,直到前一天才想起來。"], ["和伴侶大吵一架","兩邊都不願意先低頭。"], ["收到結婚暗示","他第一次認真想到婚姻。"], ["開始籌備婚禮","場地、桌數、預算讓他壓力暴增。"], ["婚後第一次重大爭執","生活習慣的小問題累積成大問題。"], ["突然被分手","他一整週都沒有心情看群組。"] ], "工作":[ ["被主管公開稱讚","他難得對工作很有成就感。"], ["被客人無理投訴","他下班後還在氣。"], ["被臨時調店","原本的生活節奏全部被打亂。"], ["遇到很合得來的新同事","上班突然沒那麼難熬。"], ["和同事鬧翻","連休息時間都變得尷尬。"], ["被要求帶新人","他第一次有了前輩感。"], ["拿到一筆意外獎金","他立刻開始想要買什麼。"], ["考慮換工作","穩定與改變之間讓他猶豫。"], ["升成小主管","權力不大,但足以讓他很興奮。"], ["上班遲到造成大混亂","他一路狂奔還是沒趕上。"], ["排班連續爆炸","他覺得整個月都在工作。"], ["突然獲得長假","他開始瘋狂規劃要去哪裡。"] ], "日常":[ ["手機掉進水裡","所有群組通知瞬間安靜。"], ["悠遊卡突然找不到","他把包包翻了三遍。"], ["買東西抽中發票獎","金額不大,但他非常得意。"], ["在路上撿到錢包","他開始煩惱怎麼處理最妥當。"], ["剪了一個自己很滿意的髮型","他今天自拍明顯變多。"], ["睡過頭錯過重要行程","醒來時整個人都傻了。"], ["鄰居半夜太吵","他忍了幾天終於爆氣。"], ["家裡突然停電","原本安排好的娛樂全部中斷。"], ["買到超難吃的便當","他認真寫了一篇長篇抱怨。"], ["意外養成晨間散步習慣","心情與生活節奏慢慢改善。"], ["整理房間翻出多年以前的東西","一整晚都在懷舊。"], ["搭車坐過站","回過神時已經完全不在預定地點。"], ["雨天沒帶傘","狼狽程度超乎預期。"], ["突然迷上新的手機遊戲","連續一季都在研究攻略。"], ["開始學做一道料理","第一鍋的成果相當可疑。"] ], "社交":[ ["在聚會上被陌生人嗆了一句","大鴿當場臉色就變了。"], ["和路人發生推擠衝突","場面一度很難看,但最後被旁人拉開。"], ["在球場和別隊球迷吵起來","周圍的人開始勸架。"], ["被朋友放鴿子","他等了很久才知道對方不來。"], ["意外成為聚會中心人物","所有人都在聽他講故事。"], ["老朋友突然道歉","多年以前的心結有機會解開。"], ["被誤認成別人","對方還非常堅持自己沒有認錯。"], ["有人公開替他說話","他對那個人的好感大幅上升。"], ["被群友聯手惡作劇","一開始很氣,後來自己也笑了。"], ["參加婚宴遇到多年不見的人","整晚都在交換近況。"], ["聚餐時被迫坐在最尷尬的位置","左右兩邊剛好都是曾吵過架的人。"], ["突然收到多年沒聯絡朋友的邀約","他不知道該不該去。"] ], "財務":[ ["買到一個事後後悔的昂貴物品","刷卡後才開始思考必要性。"], ["投資小賺一筆","他開始覺得自己可能很有天分。"], ["投資判斷失誤","這一季的娛樂預算直接縮水。"], ["突然收到一筆退款","原本以為拿不回來的錢回來了。"], ["信用卡帳單比預期高很多","他開始逐筆檢查消費。"], ["朋友找他合資買東西","他覺得很划算但又有點怕。"], ["意外接到短期兼差","收入增加但休息時間變少。"], ["中了小獎","他開始幻想下一次中大的。"], ["遺失一筆現金","他沿路走回去找了兩遍。"], ["決定開始認真存錢","第一次替自己設下明確目標。"] ], "棒球":[ ["現場看到一場超級逆轉勝","他回家後還在重播最後半局。"], ["支持的球隊慘遭再見逆轉","他整晚不想看任何體育新聞。"], ["搶到夢寐以求的球賽門票","接下來幾季都在期待。"], ["重要球員突然受傷","他開始研究替代人選。"], ["預測爆冷命中","群組裡突然很多人叫他老師。"], ["連續預測全部失敗","他開始強調『棒球本來就很難預測』。"], ["在球場抽到紀念品","他一路拿在手上不肯收進包包。"], ["支持的球員被交易","他花了一整晚消化。"], ["季後賽一輪遊","群組裡沒有人敢先提。"], ["冠軍夜","他興奮到凌晨都沒睡。"], ["和博士對同一場比賽給出完全相反預測","所有人都在等誰翻車。"], ["博士罕見地預測神準","大鴿看起來比輸球還不舒服。"] ], "遊戲":[ ["抽卡連續爆死","資源清空卻什麼都沒有。"], ["單抽出奇蹟","他立刻截圖發遍所有群。"], ["帳號突然登入異常","他以為多年心血要沒了。"], ["更新後主力卡被削弱","他開始研究新的陣容。"], ["活動最後一天才發現漏做任務","整晚都在趕進度。"], ["遇到一個超強對手","輸完後開始研究對方配置。"], ["意外進入排行榜","他截圖保存了很多次。"], ["朋友送他一個稀有道具","他對那個朋友的信任暴增。"], ["遊戲突然宣布停服","他第一次認真回顧自己投入的時間。"], ["退坑三個月後又回鍋","熟悉的介面讓他瞬間復活。"] ], "家庭":[ ["家族聚餐被問感情狀況","他整場都在閃避問題。"], ["家人突然需要他幫忙跑腿","原本的球賽計畫被迫取消。"], ["和家人為小事冷戰","幾天後誰都不記得起因。"], ["收到親戚結婚消息","他開始算自己要包多少。"], ["家裡添購新家電","他研究規格研究到半夜。"], ["家人送他一份意外的禮物","他嘴上嫌棄,實際上很高興。"], ["家族旅行成行","出發前大家就先為行程吵了一輪。"], ["被催婚","他迅速把話題轉到棒球。"] ], "人生轉折":[ ["突然決定搬家","他第一次認真盤點自己擁有的所有東西。"], ["考慮離開熟悉的工作環境","改變看起來很誘人,也很可怕。"], ["決定暫時退出所有群組","他想知道沒有聊天室的生活是什麼樣子。"], ["重新聯絡多年以前的重要朋友","一段被中斷的人際關係重新開始。"], ["突然想完成年輕時沒做的事","他列出一張奇怪但認真的願望清單。"], ["開始思考退休生活","第一次發現人生真的走了很長一段。"], ["成為某個晚輩的前輩","他開始把自己的經驗講得像傳奇。"], ["決定寫下自己的社群回憶錄","第一章寫了三天還沒離開前言。"] ] }; const GENERIC_ACTIONS=[ ["真心站在他這邊",{mood:10,trust:9,stress:-7,lonely:-5},"你這次沒有任何花招,確實幫了他。","comfort"], ["先安慰,再偷偷看戲",{mood:4,trust:4,suspicion:3},"表面穩住局面,內心則完整記錄。","prank"], ["故意講一句最刺耳的話",{anger:22,mood:-15,trust:-8,suspicion:9},"一句話讓整件事直接升級。","prank"], ["把博士扯進來",{anger:16,doctorRivalry:14,suspicion:6,doctorRelation:5},"原本跟博士無關的事,現在有關了。","doctor"], ["把事情誇大十倍傳出去",{anger:28,social:-12,trust:-12,suspicion:16},"事件從私人小事變成整個社群都知道。","prank"], ["假裝完全不知道",{trust:1,suspicion:-2},"你選擇維持無辜形象。","neutral"], ["勸他做一個衝動決定",{mood:5,stress:10,suspicion:4},"他真的開始認真考慮這個荒唐方案。","prank"], ["幫他收拾善後",{trust:13,mood:8,stress:-13},"事情變大後,你反而成了最可靠的善後者。","comfort"] ]; function proceduralLifeEvent(){ const cats=Object.keys(LIFE_CATS), cat=pick(cats), seed=pick(LIFE_CATS[cat]); let opts=GENERIC_ACTIONS.slice().sort(()=>rng()-.5).slice(0,5); // context-specific sixth choice if(cat==="感情") opts.push(["鼓勵他把感情當人生主線",{love:18,mood:8,trust:5,stress:4},"大鴿開始把更多心力投入這段關係。","comfort","love"]); else if(cat==="工作") opts.push(["叫他乾脆辭職",{career:-18,stress:-5,mood:7,trust:1},"一句氣話差點變成人生轉折。","prank","career"]); else if(cat==="財務") opts.push(["叫他梭哈娛樂預算",{wealth:-20,mood:8,stress:12},"這一季的財務直接進入高波動模式。","prank","money"]); else if(cat==="社交") opts.push(["當場幫他出頭",{trust:14,anger:-5,social:8},"你站到他前面,這件事他記了很久。","comfort"]); else opts.push(["什麼都不做",{mood:0},"人生有時候不需要你介入。","neutral"]); return [cat,()=>seed[0],seed[1],opts]; } const ARC_DEFS=[ {name:"婚姻大事件",quarters:3,steps:[ ["感情","大鴿開始認真談婚事","對方與大鴿開始討論未來,事情不再只是約會。"], ["感情","雙方第一次談到婚禮預算","浪漫瞬間變成 Excel 等級的現實問題。"], ["家庭","見家長月","大鴿罕見地提前好幾天開始緊張。"], ["感情","婚禮籌備爆炸","桌數、場地與邀請名單同時出問題。"], ["感情","婚禮前夕","他突然問你:『我真的要結婚了欸。』"], ["人生轉折","大鴿結婚了","這一天成為他人生傳記裡無法略過的一章。"] ]}, {name:"社群戰爭",quarters:3,steps:[ ["社群","一句話引爆大型群組衝突","原本兩個人的爭執迅速分成兩派。"], ["社群","截圖開始外流","每個人手上的版本都不一樣。"], ["博士","博士突然加入戰局","博士提出自己的理論,局勢更加不可控制。"], ["社群","大鴿宣布退群","這次看起來不像氣話。"], ["社群","返群投票出現","所有人都在等最後票數。"] ]}, {name:"倒楣半年",quarters:3,steps:[ ["倒楣","手機先壞了","大鴿說這應該就是本季最倒楣的事。"], ["工作","工作又出包","他開始懷疑是不是流年不利。"], ["財務","意外支出接著出現","荷包也加入災情。"], ["社交","和人發生推擠衝突","這次真的差點演變成肢體衝突,最後被旁人拉開。"], ["棒球","支持球隊關鍵戰又輸","連最後的精神寄託都不配合。"], ["好運","倒楣期終於出現轉機","一件意外的好事讓他重新振作。"] ]}, {name:"博士大戰",quarters:4,steps:[ ["博士","博士公開一套全新理論","大鴿第一時間就表示不同意。"], ["棒球","兩人預測正面對決","群組直接分成兩派下注誰會翻車。"], ["博士","博士第一回合領先","大鴿開始強調樣本數還不夠。"], ["社群","雙方支持者互丟舊圖","戰火開始脫離原本的棒球問題。"], ["博士","博士突然神隱","沒有對手的大鴿反而覺得奇怪。"], ["博士","博士回歸","第一句話就重新點燃戰火。"], ["棒球","最終決戰月","結果將成為這一世的重要傳說。"] ]} ]; function maybeStartArc(){ if(S.arc || S.age<28) return; let p=.018; if(S.love>58)p+=.018; if(S.doctorRivalry>78)p+=.014; if(S.stress>75)p+=.012; if(chance(p)){ const def=pick(ARC_DEFS); S.arc={def,idx:0}; S.bioEvents.push(`${S.year}年,一場持續數季的「${def.name}」開始了。`); } } function arcEvent(){ const a=S.arc, step=a.def.steps[a.idx]; const opts=GENERIC_ACTIONS.slice().sort(()=>rng()-.5).slice(0,6); return [step[0],()=>`【${a.def.name} ${a.idx+1}/${a.def.steps.length}】${step[1]}`,step[2],opts]; } const CAMEO_POOL=[ {name:"西雅兔哥",role:"資深反鴿人士",traits:["退群觀察","冷面吐槽"],lines:["今天的太陽是不是特別耀眼?","我只是路過。","這次我先不表態。"]}, {name:"柳丁哥",role:"火爆型群友",traits:["群戰","正面輸出"],lines:["要吵就一次講完。","我沒有在跟你客氣。","這群今天真的很熱鬧。"]}, {name:"盤咕",role:"理性派高材生",traits:["借款梗","財務腦"],lines:["先把帳算清楚再說。","金額不是重點,信用才是。","我只提供一次建議。"]}, {name:"醬財",role:"表面摯友",traits:["情報交換","現場派"],lines:["我什麼都不知道喔。","你先冷靜,我幫你看看。","這件事怎麼又變成這樣。"]}, {name:"貓咪悠",role:"中間人",traits:["調停","訊息轉接"],lines:["你們兩邊先不要吵。","我只是幫忙傳話。","這段我不方便評論。"]}, {name:"汪達",role:"預測權威",traits:["預測","群組邀請"],lines:["連勝不是靠信仰。","先看數據。","我沒有保證一定準。"]}, {name:"海稻鴿",role:"第一線觀察員",traits:["球場情報","採訪感"],lines:["欸,今天也來看球?","我只是想確認一下。","現場氣氛比群組精彩。"]}, {name:"安輪",role:"話術型鴿黑",traits:["假和解","心理戰"],lines:["大家都是朋友啦。","誤會解開就好。","你看,根本沒有人針對你。"]}, {name:"大政",role:"知名反指標",traits:["冥燈","預測梗"],lines:["我覺得今天穩。","這次真的不會翻。","不要再拿以前說嘴。"]}, {name:"養肌",role:"老群管理者",traits:["記事本","群規"],lines:["群規先看一下。","無關內容請刪除。","不要再測試管理員底線。"]}, {name:"郭全",role:"高壓調停者",traits:["要求道歉","壓場"],lines:["該道歉就道歉。","今天把事情講清楚。","我不想再看第二次。"]}, {name:"許仲凱",role:"截圖史官",traits:["公開紀錄","考古"],lines:["我只是留個紀錄。","截圖不會說謊。","幾年後還是會有人看到。"]}, {name:"呂翼宏",role:"宿敵型群友",traits:["互嗆","退群"],lines:["你先不要扯我。","這件事沒完。","我退出還不行嗎。"]}, {name:"小逄",role:"群戰終結者",traits:["補刀","最後一擊"],lines:["我只補一句。","怎麼又退了?","這麼快就沒了。"]}, {name:"陳柏紅",role:"小群創辦人",traits:["創群","管理混亂"],lines:["我再開一個新群。","這次規則會比較完整。","管理員還缺人。"]}, {name:"蓋瑞",role:"另立門戶管理者",traits:["副管","清黨"],lines:["群要重新整理。","先把權限收回來。","這次審核會嚴格一點。"]}, {name:"莫提斯",role:"反叛大將",traits:["爆噴","轉陣營"],lines:["我忍很久了。","今天就一次說完。","從現在開始我自己來。"]}, {name:"李斯寧",role:"辨識型鴿黑",traits:["認帳號","追蹤改名"],lines:["這個打字習慣很熟。","又換名字了嗎?","我先觀察一下。"]}, {name:"郝小嬤",role:"梗圖型群友",traits:["貼圖","起鬨"],lines:["我只丟一張圖。","這張剛好適合。","你不要自己對號入座。"]}, {name:"大正丸",role:"預測派勁敵",traits:["反指標","互臭"],lines:["我看法剛好相反。","這次你先說。","結果出來再講。"]} ]; function statLabel(k){ return ({ mood:"心情",anger:"怒氣",trust:"信任",suspicion:"懷疑",stress:"壓力",lonely:"孤獨", social:"社群聲望",baseball:"棒球熱情",doctorRivalry:"博士競爭",doctorRelation:"博士關係", wealth:"財務",career:"事業",love:"感情",health:"生活狀態" })[k]||k; } function deltaHTML(d,special){ const entries=Object.entries(d||{}).filter(([k,v])=>typeof v==="number"&&v!==0); let chips=entries.map(([k,v])=>{ const cls=v>0?"plus":"minus"; const sign=v>0?"+":""; return `${statLabel(k)} ${sign}${Math.round(v)}`; }); if(special)chips.push(`可能觸發特殊結果`); return chips.length?`
${chips.join("")}
`:""; } function chooseCameos(){ let n=chance(.16)?2:chance(.58)?1:0; if(S && S.arc)n=Math.max(1,n); let pool=CAMEO_POOL.slice(), out=[]; for(let i=0;i`
${c.name}${c.role}
${pick(c.lines)}
${c.traits.map(t=>`${t}`).join("")}
`).join(""):`
這一季沒有傳奇人物亂入。
`; } function cameoModifier(kind){ let mod={}; for(const c of (S.cameos||[])){ if(kind==="doctor" && (c.traits.includes("預測")||c.traits.includes("反指標"))) mod.doctorRivalry=(mod.doctorRivalry||0)+3; if(kind==="prank" && (c.traits.includes("貼圖")||c.traits.includes("起鬨")||c.traits.includes("補刀"))) mod.suspicion=(mod.suspicion||0)+2; if(kind==="comfort" && (c.traits.includes("調停")||c.traits.includes("壓場"))) mod.trust=(mod.trust||0)+2; } return mod; } function blockRiskLabel(){ if(S.blocked)return "已封鎖"; const r=S.suspicion*0.55+S.anger*0.25+S.exposure*0.65-S.trust*0.2; return r>95?"極高":r>70?"高":r>45?"中":r>20?"低":"很低"; } function endingRiskLabel(){ let r=0; if(S.blocked)r+=28; if(S.suspicion>85)r+=25; if(S.trust<15)r+=25; if(S.exposure>90)r+=25; if(S.social<10)r+=10; if(S.stress>90)r+=8; return r>=70?"極高":r>=45?"高":r>=25?"中":"低"; } function npcWorldLabel(){ const vals=Object.values(S.npcMemory||{}); if(vals.filter(x=>x.hostility>60).length>=4)return "群雄混戰"; if(vals.filter(x=>x.friendship>65).length>=4)return "好友聯盟"; return S.arc?"劇情風暴":"穩定"; } function ensureNpcMemory(){ if(!S.npcMemory)S.npcMemory={}; for(const c of CAMEO_POOL){ if(!S.npcMemory[c.name])S.npcMemory[c.name]={friendship:Math.floor(rng()*31+35),hostility:Math.floor(rng()*26),left:false,alliance:null}; } } function updateNpcWorld(kind){ ensureNpcMemory(); const cs=S.cameos||[]; for(const c of cs){ let m=S.npcMemory[c.name]; if(kind==="comfort"){m.friendship=clamp(m.friendship+4);m.hostility=clamp(m.hostility-2)} if(kind==="prank"){m.hostility=clamp(m.hostility+5)} if(kind==="doctor"){m.friendship=clamp(m.friendship+(c.traits.includes("預測")?3:0));m.hostility=clamp(m.hostility+2)} if(m.hostility>82 && chance(.18)){m.left=true;S.log.push(`${S.year} Q${S.quarter}|${c.name} 因長期衝突退出主要社群。`)} if(m.left && chance(.08)){m.left=false;S.log.push(`${S.year} Q${S.quarter}|${c.name} 又悄悄回到社群。`)} } // NPC vs NPC random alliance/conflict if(chance(.22)){ const a=pick(CAMEO_POOL),b=pick(CAMEO_POOL.filter(x=>x.name!==a.name)); const ma=S.npcMemory[a.name],mb=S.npcMemory[b.name]; if(chance(.5)){ ma.alliance=b.name;mb.alliance=a.name; S.log.push(`${S.year} Q${S.quarter}|${a.name} 與 ${b.name} 暫時結盟。`); }else{ ma.hostility=clamp(ma.hostility+8);mb.hostility=clamp(mb.hostility+8); S.log.push(`${S.year} Q${S.quarter}|${a.name} 與 ${b.name} 公開互嗆,群像世界升溫。`); } } } function maybeBlock(){ if(S.blocked)return; let p=0.01 + S.suspicion/650 + S.exposure/850 + S.anger/1200 - S.trust/1600; if(S.suspicion>80)p+=.07; if(S.exposure>80)p+=.06; if(chance(Math.max(.005,p))){ S.blocked=true;S.blocks++;S.trust=clamp(S.trust-18);S.suspicion=clamp(S.suspicion+10); S.log.push(`${S.year} Q${S.quarter}|大鴿封鎖了你。`); S.bioEvents.push(`${S.year}年,大鴿曾正式封鎖這名好友。`); } } function unblockEvent(){ return ["關係",()=>`你被大鴿封鎖了`,`訊息送不出去。你要怎麼爭取解封?`,[ ["請共同好友幫忙說情",{trust:5,suspicion:-7,stress:-3},"有人願意幫你轉達。","comfort","unblock_try"], ["寫一段非常誠懇的道歉",{trust:9,suspicion:-10,anger:-8},"你把姿態放到最低。","comfort","unblock_try"], ["假裝一切都是誤會",{trust:-2,suspicion:-3,anger:-2},"這個說法有機會,也可能讓他更火。","neutral","unblock_try"], ["找博士當中間人",{doctorRelation:8,doctorRivalry:8,suspicion:2},"這是一個相當危險的調停人選。","doctor","unblock_try"], ["換個管道繼續接近",{suspicion:15,trust:-8,exposure:12},"這會大幅提高身分暴露與強制結局風險。","prank","unblock_try"], ["接受封鎖,等一季",{suspicion:-8,anger:-7,trust:-2},"你選擇先消失。","neutral","wait_block"] ]]; } function resolveUnblock(special){ if(special==="wait_block")return; let chanceBase=.28 + (100-S.suspicion)/260 + S.trust/330 - S.exposure/400; if(special==="unblock_try" && chance(chanceBase)){ S.blocked=false;S.log.push(`${S.year} Q${S.quarter}|大鴿解除了對你的封鎖。`); S.bioEvents.push(`${S.year}年,你成功重新取得大鴿聯絡管道。`); }else if(special==="unblock_try"){ S.suspicion=clamp(S.suspicion+5);S.log.push(`${S.year} Q${S.quarter}|解封嘗試失敗。`); } } function maybeForcedEnding(){ let reasons=[]; if(S.blocked && S.suspicion>92 && S.exposure>92)reasons.push("永久封鎖"); if(S.trust<=3 && S.suspicion>85)reasons.push("關係徹底決裂"); if(S.exposure>=98)reasons.push("假好友身分完全曝光"); if(S.blocks>=5 && S.trust<20)reasons.push("反覆封鎖耗盡關係"); if(S.social<=2 && S.exposure>80)reasons.push("社群全面識破"); if(reasons.length && chance(.22)){ S.forcedEnding=pick(reasons);return true; } // low-probability dramatic endings if(S.quartersPlayed>12 && chance(.0025)){ S.forcedEnding=pick(["大鴿徹底退出所有社群","你主動坦白假好友身分","博士意外揭露你的雙面操作"]); return true; } return false; }