对局日志不再什么都念一遍
过去掷一次骰子就要打印五行,其中两行说的是同一个格子。现在这条流读起来像解说而不是笔录——而且没有为此删掉任何东西。
通俗版
棋盘旁边那块面板把游戏做过的每一件事都记了下来。一次普通的掷骰会产生五条独立的记录——掷了、移动了、落在哪、结束回合,往往还多一条——其中两条说的是同一个格子。再乘以四名玩家、三十个回合,这条流就成了你不再去看的一堵墙;这很可惜,因为真正重要的那两三行就藏在里面。
现在一次掷骰和它引发的一切合成一行。落格、结束回合、在支付之前先宣布一笔欠款、在拍卖开始前拒绝购买——这些都不再念出来,因为棋盘或下一行已经说明了。同一块地上的一连串建造合并成一行,无人竞价的拍卖不再把同一个金额报两遍,而在极速模式里,抵押倒计时只在还剩两回合可以处理时才出声。
你的聊天消息也不会再被挤出屏幕。面板以前对所有内容只保留一个历史窗口,所以繁忙的棋盘会悄悄把别人打的字挤掉。现在活动和聊天分开保存,再多的骰子也埋不掉一条真人写下的消息。
技术细节
在源头过滤从来都不是一个选项。回放一局游戏意味着把完整的事件日志折叠回一个状态,而页面刷新、旁观、回放视图和集锦渲染器都是这么工作的。为了让日志好看而丢掉一个事件,就会把这些全部弄坏。因此日志保持完整,压缩只是渲染层的事,放在一个共享的构建器里,被实时棋盘、旁观视图、回放、集锦和离线版本共同使用。
每个事件对日志的影响是一个覆盖引擎全部事件类型的穷举 switch,这意味着新增一种事件在有人决定它该怎么念之前根本无法通过编译:
// lib/game/feedDigest.ts — an exhaustive switch over every engine event
case 'dice_rolled':
return 'move'; // the roll opens a line
case 'moved':
case 'passed_start':
case 'jail_attempt_failed':
return 'absorb'; // …and what it caused folds into that line
case 'landed': // names the cell 'moved' just named
case 'turn_ended': // the board already shows whose turn it is (14% of events)
case 'payment_due': // the intent; 'debt_paid' is the settlement, with the amount
case 'buy_declined': // always followed by 'auction_started', which says it better
case 'auction_passed':// carries no information, and outnumbers real bids 2:1
return 'hidden';有两个缺陷是任何测试都找不出来的,因为它们关乎输出读起来如何,而不是输出包含什么。无人竞价的拍卖会把同一个数字报两遍——「P1 出价 2600k」,然后「P1 以 2600k 拿下」——所以现在收官那一次出价会被吞掉,而且只吞这一次,真正的竞价战读起来仍然像一场争夺。另外,夹在掷骰中间的一个事件会让每一条移动记录提前结束,直到构建器学会跨过它。
