简介这是一款由英国《金融时报》制作的叙事性新闻游戏围绕优步司机的经济处境与真实生活展开玩家需在一周内尝试赚取1000美元并在接受采访的真实司机面前做出抉择。资源面向对零工经济、交互叙事与新闻游戏开发感兴趣的读者也适合作为JavaScript前端项目的学习案例。压缩包共78个文件约517KB以28个js脚本、11个html页面、9个json配置为主辅以yml、ts、scss、md等涵盖客户端组件、服务端逻辑、样式与构建配置目录结构清晰。目前已有300人学习下载。通过该资源读者可了解叙事游戏的状态管理与决策分支设计参考真实采访素材的呈现方式并借助npm或yarn快速安装运行体验完整的开发与部署流程。1. 当叙事遇上算法一款新闻游戏如何把司机生存账本摊开一款叫 uber-driver-game 的叙事性新闻游戏把网约车司机的经济账和生活账做成了可交互的模拟系统。它不教你开车也不复刻接单流程而是把「跑一单到底赚多少、平台抽成怎么算、租车还是买车、每天跑几小时才够本」这些散落在论坛和访谈里的碎片压缩成一套能反复试错的决策模型。目标读者是三类人想用游戏做数据叙事的开发者、研究零工经济的产品经理、以及想给自己算一笔明白账的司机。核心问题只有一个——当平台规则变成代码司机还有多少腾挪空间这个标题背后不是娱乐产品而是一套把收入结构、时间成本、车辆折旧和平台激励拆开揉碎的可视化沙盘。接下来我会按「先立模型、再跑仿真、最后看边界」的顺序把这类新闻游戏的落地路径讲清楚。2. 把司机的一天拆成状态机收入模型怎么建才不飘2.1 先定状态在线、接单、空驶、休息四个格子任何叙事性模拟的起点都是状态划分。网约车司机的一天看似连续实际可以离散成四个互斥状态在线待单、载客中、空驶寻客、离线休息。每个状态对应不同的时间消耗和现金流入流出。常见做法是用一个带时间步长的循环来推进步长取 1 分钟因为平台计费通常按分钟和公里双轨计算1 分钟粒度足够还原大部分城市的计价规则。状态转移不是随机的而是由「当前区域供需比」「司机接单策略」「平台派单逻辑」三者共同决定。新手容易把接单写成纯概率结果跑出来的收入曲线平滑得像教科书完全不像真实司机那种「半小时没单、一来来三单」的脉冲感。我一般会引入一个区域热度系数让接单概率随时间波动波动周期参考早晚高峰和夜宵时段。# 司机状态机核心循环简化版 import random STATES [idle_online, en_route, occupied, offline] STEP_MINUTES 1 SIM_HOURS 12 def simulate_day(area_heat, base_fare, per_km, per_min, platform_cut): state idle_online cash 0.0 minutes_online 0 for minute in range(SIM_HOURS * 60): if state offline: break minutes_online 1 # 区域热度决定接单概率热度高则等待短 if state idle_online: if random.random() area_heat[minute // 60]: state en_route elif state en_route: # 空驶 3~8 分钟到达上车点 if random.random() 1 / random.randint(3, 8): state occupied elif state occupied: # 载客 8~25 分钟按里程和时长计费 trip_min random.randint(8, 25) trip_km trip_min * random.uniform(0.3, 0.6) fare base_fare per_km * trip_km per_min * trip_min cash fare * (1 - platform_cut) state idle_online return cash, minutes_online这段代码的逻辑说明area_heat是一个长度 12 的列表代表每小时区域热度值越高接单越快base_fare是起步价per_km和per_min是里程费和时长费platform_cut是平台抽成比例。参数怎么改如果模拟的是租车司机要在每日固定成本里加一笔租金如果是自有车则把折旧摊到每公里。失败时看什么如果跑出来的时薪远高于当地司机访谈的中位数大概率是空驶时间给太短把en_route的等待分布调长即可。2.2 成本项别漏油电、折旧、保险、租金一个都不能少收入模型跑通只是半张账本另外半张是成本。很多模拟只扣平台抽成结果司机时薪虚高 30% 以上。真实成本至少四块能源费油或电、车辆折旧、保险与保养、以及租车司机的日租金。能源费按每公里单价乘总里程算折旧按购车价除以预计总公里数摊保险保养按年费除以年行驶天数摊。这里有个容易翻车的地方折旧不能按时间摊要按里程摊。一辆车跑 10 万公里和跑 5 万公里残值差一大截。我一般会设一个depreciation_per_km参数默认 0.3 到 0.5 元每公里具体看车型。租金则是租车司机每天睁眼就欠的钱不管跑不跑都要扣这个压力在模拟里要体现出来否则叙事张力少一半。# 成本计算模块 def daily_cost(total_km, is_rented, rent_per_day, energy_per_km, depreciation_per_km, insurance_per_day, maintenance_per_km): energy total_km * energy_per_km depreciation total_km * depreciation_per_km maintenance total_km * maintenance_per_km fixed insurance_per_day (rent_per_day if is_rented else 0) return energy depreciation maintenance fixed参数说明energy_per_km电车约 0.1 到 0.2 元油车约 0.5 到 0.7 元depreciation_per_km经济型车约 0.3 元中档车约 0.5 元insurance_per_day按年保费除以 300 天估算maintenance_per_km含轮胎和保养约 0.1 元。把这些加总后从收入里扣得到的才是净现金流。如果净现金流为负模拟里司机应该有权选择收车这个退出机制是叙事游戏的关键分支。2.3 平台激励要当变量而不是常量平台冲单奖、高峰溢价、连续在线奖励这些在真实司机收入里占比不小但规则经常变。模拟里不能写死要抽成可配置的激励规则表。常见做法是定义一个incentive_rules列表每条规则包含触发条件如完成单数、在线时长、时段和奖励金额。跑仿真时逐条检查是否满足满足则累加。这样做的价值在于你可以快速对比「有奖励」和「没奖励」两种世界线下司机的时薪差异也能模拟奖励门槛提高后司机需要多跑多少单才能拿到。参数怎么设冲单奖通常分三档比如 10 单奖 15 元、20 单奖 40 元、30 单奖 80 元门槛越高边际收益越大但司机为了冲最后一档往往要额外空驶这部分成本要算进去。3. 用蒙特卡洛跑一千天把「运气」从收入里剥离出来3.1 单次模拟不够要跑分布司机收入波动极大同一天同城市两个人可能差一倍。单次模拟只能告诉你「可能发生什么」跑一千次才能告诉你「大概率发生什么」。蒙特卡洛方法在这里不是炫技而是刚需。把接单间隔、行程长度、区域热度都设成随机变量每次模拟抽一组参数跑完一千天得到净收入的分布。import numpy as np def monte_carlo(runs1000): results [] for _ in range(runs): heat [random.uniform(0.3, 0.9) for _ in range(12)] cash, mins simulate_day(heat, base_fare8, per_km2.2, per_min0.5, platform_cut0.25) km mins * random.uniform(0.2, 0.4) cost daily_cost(km, is_rentedTrue, rent_per_day120, energy_per_km0.15, depreciation_per_km0.35, insurance_per_day15, maintenance_per_km0.1) results.append(cash - cost) return np.percentile(results, [10, 50, 90])逻辑说明np.percentile取 10%、50%、90% 分位分别代表倒霉日、普通日、幸运日。参数怎么改如果想知道电车和油车的差异把energy_per_km从 0.15 改成 0.6 再跑一遍。失败时看什么如果 90 分位和 10 分位差距超过三倍说明随机变量范围设太宽检查heat的上下限是否合理。3.2 敏感性分析哪个参数最影响时薪跑完分布后下一步是找出哪个参数对净收入影响最大。做法很简单固定其他参数只让一个参数在合理范围内变动观察时薪变化幅度。常见结论是平台抽成和空驶率影响最大能源单价影响中等保险保养影响较小。这个排序直接指导叙事重点——游戏里应该让玩家优先感受到抽成和空驶的压力。参数变动范围时薪变化幅度平台抽成15%30%高空驶时间占比20%50%高能源单价0.10.7 元/公里中日租金80180 元中保险保养1030 元/天低表格里的结论来自多次仿真对比不是拍脑袋。做叙事游戏时把高影响参数做成玩家可调选项低影响参数收进高级设置界面才不会劝退。3.3 把结果映射回叙事数字要能讲出人的选择模拟跑出来的分布不能直接甩给玩家看要转译成叙事语言。比如 10 分位净收入 80 元对应「今天白跑还倒贴油钱」50 分位 220 元对应「够吃饭交租但攒不下」90 分位 450 元对应「今天运气好但明天呢」。每个分位配一段司机独白或短信对话玩家看到的就不是冷冰冰的百分位而是活生生的处境。这一步的技术含量不在代码在文案和数值的对应关系。我一般会先跑出分布再按分位切五档每档写三到五条短叙事随机抽一条展示。注意别把高收入档写成「人生赢家」真实司机的高收入日往往伴随超长在线和极端疲劳叙事里要保留这个代价。4. 避坑与排查这类新闻游戏最容易翻车的五个地方4.1 收入虚高空驶时间被吃掉了现象模拟时薪跑到 40 元以上远超司机访谈里的 20 到 30 元。原因空驶等待时间设太短或者接单概率设太高。解决把en_route状态的等待分布调长早晚高峰外区域热度降到 0.3 以下并加入「派单距离超过 3 公里司机可能拒单」的逻辑拒单后重新等待。4.2 成本漏项只扣抽成不扣折旧现象净收入比司机实际到手高出一大截。原因折旧、保险、保养没摊进去或者只算了油费。解决按里程摊折旧按天摊保险按公里摊保养租车司机加日租金。跑一遍成本模块确保总成本占流水比例在 55% 到 75% 之间低于这个区间说明漏项。4.3 随机性失控每天收入像过山车现象连续模拟十天最高和最低差五倍。原因随机变量范围设太宽或者没有引入均值回归。解决给区域热度加一个基础值随机波动限制在基础值上下 30% 以内行程长度用对数正态分布而不是均匀分布避免极端值频繁出现。4.4 激励规则写死改一个数要动十行代码现象想调整冲单奖门槛发现规则散落在各处。原因没有把激励抽成独立配置。解决用列表存规则每条规则一个字典包含条件类型、阈值、奖励金额。主循环只负责遍历规则并累加改参数只动列表不动逻辑。4.5 叙事和数值脱节玩家感觉不到数字背后的压力现象模拟结果准确但玩家看完没感觉。原因只展示数字没有把数字翻译成生活场景。解决按分位切档配文案把「净收入 80 元」写成「今天跑完油钱刚够晚饭还没着落」。数值是骨架叙事是血肉缺一个都立不住。5. 进阶技巧用反事实模拟让玩家看见「如果」5.1 反事实分支同一司机不同规则这类新闻游戏最有价值的玩法不是「跑一天赚多少」而是「如果平台抽成降五个点司机的日子会怎样」。实现方式是在同一组随机种子下跑两次模拟只改一个参数把两条净收入曲线叠在同一张图上。玩家拖动滑块调整抽成比例曲线实时变化视觉冲击比任何文字都有力。def counterfactual(seed, cut_a, cut_b): random.seed(seed) heat [random.uniform(0.3, 0.9) for _ in range(12)] cash_a, mins_a simulate_day(heat, 8, 2.2, 0.5, cut_a) random.seed(seed) cash_b, mins_b simulate_day(heat, 8, 2.2, 0.5, cut_b) return cash_a, cash_b关键点是random.seed(seed)要重置两次保证两次模拟面对完全相同的「运气」唯一变量是抽成比例。这样对比才干净否则玩家会质疑「是不是第二次运气好」。参数怎么设cut_a设当前平台抽成cut_b设假设值差值建议在 5 到 15 个百分点之间太小看不出差异太大不真实。5.2 时间维度拉长从一天到一年单日模拟只能反映波动拉长到一年才能看出累积效应。把每日净收入按顺序累加画一条年度现金流曲线再叠加车辆大保养、保险续费、年检这些年度支出节点。玩家会直观看到某些月份净收入为正但一到续保月就转负。这种「锯齿形」现金流是零工经济的典型特征也是叙事的高光点。实现上把每日模拟包一层年度循环维护一个cash_balance变量每天加上净收入遇到年度支出节点就扣一笔。如果cash_balance低于某个阈值触发「司机考虑退出」的叙事分支。阈值设多少一般设一个月租金或两个月保险低于这个数司机心理压力会急剧上升。5.3 验证方法拿真实访谈数据做锚点模拟再漂亮如果和真实司机感受对不上就是自嗨。验证方法很朴素找三到五个司机访谈或公开的收支记录把他们的月流水、总里程、在线时长、净收入填进表格再用模拟跑同样的条件对比差异。如果模拟净收入比真实数据高 20% 以上回头查成本项如果低 20% 以上查接单概率和激励规则。我自己的习惯是先把锚点数据固定下来再调参数去逼近而不是反过来。锚点数据不用多三五个案例就能框住合理范围。超过范围就说明模型有结构性偏差不是调参能解决的得回去改状态机或成本结构。5.4 一个具体技巧把「疲劳」做成隐性成本真实司机跑久了会累累了接单效率下降、事故风险上升。模拟里可以加一个fatigue变量随在线时长累积超过阈值后接单概率下降、空驶时间变长。这个变量不直接扣钱但会拉低时薪玩家能感受到「硬撑不划算」。参数怎么设连续在线 4 小时后开始累积每多一小时接单概率降 5%降到 70% 为止。这个技巧让模拟从「算账工具」变成「体验工具」是叙事游戏和普通计算器的分水岭。我做了几轮这类模拟后最大的教训是别急着写代码先把成本项和状态转移在白板上画清楚漏一个成本项后面所有数字都是错的返工比重写还难受。希望帮到你。本文还有配套的精品资源点击获取