小名开开

在春天的光影里喘息


理想状态的英雄语音音频源文件,应该是这样的:

sample

有一个比较标准的文件名(Filename),以英雄名字起头,文件名自己就包含了一些有用的分类信息。
有一个配套的文本文件(Dictionary),里面写好了每个音频对应的台词(Text),我们直接可以查找替换复制粘贴。
有一套分类详尽的标签(Tags)列表,里面详细地给每个音频分好了类,我们可以方便地根据这些标签进行筛选、处理、归档。
这些理论上 Blizzard 应该已经做好了放在客户端里,我们只要解压了就能拿来用。

事实上并不是这样的,CASC 是 BLZ 私有的加密格式,目前并没有很好的手段可以完美解压,实际解出来的结果大概是这样的:

hashname

这种 32 个数字英文组成的字符串称之为 filehash,可以简单理解为一种特殊的文件名。这种名字没有实际意义,仅仅表示『这个文件和那个文件是不同的』。

所以,比『什么都没有』稍微好点,我们有了『不知道是什么的一堆文件』,稍后我们又通过文件格式识别软件,找出了其中的音频部分,并转换成了可以播放的 mp3。于是我们得到了『不知道是什么的一堆音频』:

untitled-2

情况没有本质变化,但我们有了一大包知道是 mp3 的文件以后,我们就可以 手-动-听-写-打-标-签 了。

于是情况变成了这样:

untitled

这就是 http://ow.thnuclub.com 这个小站现在正在做的事情。全世界想的办法都一样——先解出来能播放的,再人工一个一个挑。幸运的是,吃瓜群众撸袖自己上,一不小心撸出了全世界最好的守望音频网站。


昨天 nga 有网友(青龙圣者@ngacn.cc)推荐了 toolchain 系列解压软件。这个软件在小站建站之初是没有的。现在看来,它有优点也有缺点,但前景似乎不错。

toolchain 使用的还是 zezula 的 CASCLib 开源库,但库版本更新了不少。因此比 cascview 能多解出一些信息,具体地来说是有了目录和文件类型,有目录就意味着可以自动完成一部分标签。但缺点是引入了另一套也没啥意义的文件名系统。于是情况变成了这样:

小站的旧数据有 Text 内容,而 toolchain 有相对比较准确的标签分类。但因为两边的文件名对不上,所以不知道哪个对应哪个。

再仔细验证以后,发现 toolchain 解压的文件是可以计算得到 filehash 的,于是又变成了:

tagmatching

这是目前所能获得的最大的成果了。

进一步分析发现,toolchain 解压出的音频总数较少,也就是:

compare

原因可能是 toolchain 的作者的关注重点并不在音频上,因而把原始数据中暂时未分析的部分直接抛弃了。而 CascView 则以解压优先,并未抛弃数据。

总结:

  1. toolchain 通过目录结构间接提供了较为准确的英雄分类 tag,可以补充修正现有小站全靠人工听写的标签数据。
  2. toolchain 目前仍然没有解出音频对应的文本,而目前音频文本(Text 数据)依然是最宝贵的劳动成果,也是 ow.thnuclub.com 小站存在的最大价值。暂时还没办法由 toolchain 自动化解决,期待未来某天可以彻底解决。那时小站大概就可以关闭了。
  3. toolchain 引入了另一套文件名,但可以通过计算得到 hash 值与旧数据对应起来。
  4. toolchain 的数据分类更细致,但总量较少,新的文件名系统无甚作用,且构成规则不明。因此也没有必要跟随 toolchain 的命名方式。
  5. toolchain 可解压出 *.mdl、*.dds 等文件,对视频制作者、签名档、头像等很有作用,但对于既有的音频内容,所助仅限于标签分类数据。
  6. 对于一些特殊的标签数据,例如莱因哈特的台词『Many of my comerads fell in battle here, may they rest in peace.(我的许多同伴在此牺牲,愿他们安息)』只会在艾兴瓦尔德这张地图出现,因此较完美的 tag 应当是『莱因哈特,英雄,艾兴瓦尔德,地图,入场』,这个是 toolchain 也无法提供的,只能依靠人工标注。

因此,在现阶段 toolchain 还不成熟的情况下,暂时还没有必要修改小站现有的数据结构和使用方式,只需要把 toolchain 提供的目录结构,转化成较为准确的 tag 数据补充到小站上即可。相比于 toolchain 构成规则不明的文件名系统,可以通过计算得到的 filehash 系统通用性也更好一些。


接下来的工作:

  1. 计算 toolchain 获得的所有音频文件的 hash 值,如果有软件可以直接带子目录列表输出 csv 就好了。
  2. 根据目录路径给 toolchain 所有音频文件打上英雄分类/地图分类 tag。
  3. 合并到现有小站数据上,需要解决英雄标签冲突,并尽可能保留有效信息。
  4. 但合并 tag 也可能导致形如『【天使】I feel unstoppable.』这类音频中的安娜标签丢失。具体处理办法还需要考虑。

更新完成。

最近一段时间守望外挂封号频率变快,吃瓜群众们顺带就发现了另一乐趣:『看演员』。即是围观被封号的人员是如何花样百出地去论坛控诉他们是『被误封』的。我也去了。可不是嘛,挺开心的。

绝大部分的申冤理由最后都集中到了『被盗号开挂』上,毕竟这大概确实属于最能彻底撇清责任的理由。帐号被盗并非拥有者意愿,而被盗期间主人对帐号失去了控制权,造成的影响也应当与主人无关,既然两个环节都并非号主意愿,那么号主自然不应当承受处罚。我觉得这个申诉理由确实有点意思,就记一笔理一下这里的逻辑好了。

『帐号被盗开挂被封』,仔细想想这八个字背后的逻辑其实挺复杂的。为了说明问题逻辑,不妨先来看一下另一个例子:

你买了一辆汽车,一切手续完备资格有效,各种费用包括保险费交通费全部交清——这完完全全是你的东西,别人找不到一丝茬。然后你某天下车没有关好门,一个无业混混一拉车把,门就开了。他坐进去打算开一把过瘾,然后就撞死了个老太太,要赔 30 万。

你忘了关车门,混混偷了你的车撞死了人。好嘛,这事怎么了结?

一般来说,首先是分配责任。混混偷车还撞死人当然是主责,但你确实也忘了关门,次要责任肯定得担一些。至于比例多少基本就是法官定度了,就假设是二八开吧。于是理论上你承担 6 万,混混承担 24 万。在结案以后,汽车会还给你。至于洗车费做法事香火钱,那得你自己出。

这个例子可以有诸般变化,比如你能证明你没有疏忽,已经尽到了保管责任,有监控录像证明你确实停在车库并且锁了门,混混是自己撬门开的。那你很幸运的不用承担责任,理论上还可以找混混要求赔偿修车钱。又比如混混还未成年,你停车的地方又是学校附近,可以预见到这里未成年人多。那你忘关车门甚至可能成为主责。再比如混混出车祸一块死了。那你就安心赔上 30 万给老太太家属,说不定还得另赔 30 万给混混家属。

但就算是一堆废铁了,车还是会还给你。如果你还要,记得付拖车费。

所以照这么说,帐号=车,应该还你罗?

这里的最大区别,其实恰恰就在于帐号不等于车。

你对某辆车拥有『所有权』,意味着对这车拥有一切处置、支配的权力,改了拆了砸了埋了卖了,都行。除非法律明确禁止,比如做成汽车炸弹。但退一万步说,法律也只是禁止你危害公共安全,并不禁止你对汽车进行改装,哪怕是改造成炸弹。

实物商品的复制需要和原型等量多的材料,还需要同等的加工和运输,凝聚了诸多人类劳动,因而我们可以将每一辆车看作独立物品,并单独分配所有权。但软件不同,软件存在一个实物没有的特点,『零成本复制』。无论是代码也好,小说也好,本质上都只是一段特定的排列顺序。软件作者,小说也好,所做的工作本质是排出这段序列而已。排列是困难的,排出来了,接触到的人想要照着再排一次,却毫不费力。

为了对抗这种情况,人们发明了版权。一旦某个序列排好,作者就可以宣称对这样一段序列有所有权。全世界任何一个地方,只要出现相同的序列,作者就拥有所有权,这就是版权的本质。实物的『复制』有成本,所以所有权可以分配到物。序列的复制无成本,则所有权索性不可分割,全部属于创造者。

而客人购买的,是『接触、阅读或者使用这段序列』的权利。说人话,就是买了软件的使用权,或者书本的阅读权。对于书籍而言,作者把『一段排列好的文字』通过卖实体 / 电子书的渠道,展现在你面前。展现的期限通常都是永久的,但也存在『租书』这种有时限的情况。软件同理,你购买的是限时或者永久使用这段代码的权利。

因为所有权永远是作者的,所以『复制』作为对原型的一种处置,自然也只有作者拥有这一权利。这样就从法理上,确定了未经作者许可的『复制』(也就是盗版)是侵犯作者权力的。同时,尽管在绝大多数情况下使用时长是永久的,但购买使用权本身更像是一种『租赁』。你花钱在某网吧办了『牛逼会员』,终身免费上网,也终归只是一种特殊的包时上网,并非就拥有网吧了。

回到守望这事上。你花 198/328 购买的,首先并不是软件的『所有权』。如果你对守望有了『所有权』,则暴雪网易反过来需要向你交纳使用费了,这显然不成立。没有所有权,就排除了『任意处置』的权利。顾客购买的并非所有权,则如何使用,需要根据买卖双方的约定执行。

你把钱交给暴雪,暴雪允许你复制它的客户端,连上它架的服务器,获得愉悦。所以你购买的是『使用名为守望的这段代码获得娱乐』的权利。时长名义上是『永久』,实际上是暴雪公司的寿命。服务器本质上也只是另一段代码,客户端和服务端代码一起,才能整个正常运行起来。

讲到这里,就涉及到核心问题了:盗号开挂该不该封?

暴雪拥有守望的『所有权』,并分割出了很多份不同的『使用权』,一个客人通常会购买一份使用权,但真想要购买多份也可以(小号)。每一份使用权都在顾客和暴雪达成约定后获得,约定内容包括:

  • 支付一定的金钱(就是花钱买)。
  • 为客人准备一份使用权,并准备相应的服务资源。(例如每卖出一千份多开一组服务器)。
  • 约定这份使用权的帐号密码,包括密保等,通常由客人自行完成(注册帐号)。
  • 若干条使用权的限制(也就是用户协议),通常包括不可共享、反编译等,当然也包括不可开挂。

在这一逻辑下,帐号密码只是链接暴雪和玩家的一个环节。它本身并不是出售的使用权的一部分,而仅仅是用来和玩家约定,谁可以使用这份『使用权』而已。也就是说:

  • 对于玩家,主观感觉可能是:花了 198 / 328,买了一个游戏的使用权,只有我能用,别人用不算。
  • 对于暴雪,客观的逻辑则是:某一份特定的『使用权』已经售出。自己准备好了相应的服务资源,并和客人约定了使用这份资源的口令。当然,口令可以由双方约定得很复杂(安全令/异地IP限制),并且暴雪也建议约定得足够复杂(绑安全令送 WOW 宠物)。但用户依然可以约定得很简单。

对于暴雪而言,确认身份的唯一方法就是口令,只有拥有口令才能获得『使用权』,只要拥有口令就能拥有『使用权』。而惩罚也是针对『使用权』在行使过程中的不当行为作出的,根据情节轻重,会暂停提供使用权,几天到永久不等。也就是说,对于暴雪而言,只是针对每一份『使用权』的不当行为,削减该份『使用权』的时长罢了。

由于事先约定了口令,自然拥有该口令的连接,必须可以访问该份『使用权』。盗号者当然犯了冒用他人身份的罪,然而之所以说“冒用”,正是因为只有当事人才知道是假的,而对于善意的第三方而言,这个身份是正确有效的。

一个有效『使用权』做出了不当行为,于是削减该份『使用权』的时间。

一个有效『使用权』开了外挂,于是削减该份『使用权』所有剩余时间(即封禁)。

由冒用身份而对事主造成的损失,由冒用身份的人负责。假若在核对口令检查身份时服务方有疏忽,则服务方也有责任。也就是说,如果你不但可以证明是有人盗用你的帐号开的挂,并且还能证明暴雪在验证帐号密码上有疏忽(比如明明开了将军令没却没验证 / 错误的用户名密码也可以登录),那么确实有理由解封。但假若在验证帐号密码上没有疏忽,则你的损失应当向冒用身份的人去追索,而与服务方无关。

以上只是逻辑上而言的,但在现实中其实还有更强大的约束存在——购买时的事前约定,也就是用户协议。当用户协议与法律不冲突时,该协议无论从人伦视角来看多么过份,依然是受法律保护的有效协议。因为这是最顶层的“事前约定”。至于怎么看用户协议,就不用多说了。

问题:安装 VMWware 的机器,在打开虚拟机时无法以桥接方式连入网络,使用虚拟网络编辑器,则提示『没有未桥接的主机网络适配器』。

解决办法:

打开控制面板\网络和 Internet\网络连接

右键本地连接\属性

无标题

删除 VMWare Bridge Protocol,如果有共享设置,则所有项目都取消钩选,然后确定。

从开始菜单打开虚拟网络编辑器,点击左下角『还原默认设置』。

无标题

搞定。

更新完音频小站上线体验了一把新地图。

艾兴瓦尔德,德国小镇,智械危机中莱因哈特所在的十字军死守的小镇。十字军在此一役中几乎全军覆没,但为后方大部队的集结争取了时间,人类从而获得了反攻的机会并最终赢得了胜利。(大概是这样的剧情吧)《最后的堡垒》短片里有一段回忆也和这场战役有关。莱因哈特在这地图上也有特殊相关台词。

体验了几把,包括使用自定义跑图,感觉是对进攻方非常不友好的地图。大概是随着游戏被越来越多的玩家深入理解掌握,各地图进攻方的优势和胜率越来越大,逼得暴雪通过地图加强防御方了。

但从全局看,这图怕是更难做到攻防平衡。

A 点路口

在所有的占点推车混合图里,这图是唯一一个 A 点只有一条直路可供大部分英雄通过的了,对于同类型的努巴尼,A 点有四条路线,任何一条路线都可以集体进攻。国王大道也有右侧二楼的集体路线。即使是好莱坞也有一个侧门,虽然对于鱼塘局作用不大但也偶有奇效,况且好莱坞大门处防守方掩体不足,很难死守大门,完全阻止进攻方突进英雄从左侧绕后。

但这个新图真的差不多准心瞄准桥头就行了。桥左破房子翻过去也只能骚扰,很可能在没有技能时直接面对对面两三个英雄,右侧只有法拉 D.VA 能飞,并且也要耗费完主力技能(飞行)才能通过。占点圈也比其它地图小,并暴露在三处高点下。

进攻方大部队我想不出什么其它策略,只能正面强冲。顶多就是利用温斯顿、法拉天使双飞、源氏、猎空之类的从桥上翻跃,勉强算是半个绕后。但温斯顿单重装强冲往往瞬死,如果想再加上查莉娅盾、禅雅塔谐,那么查莉娅和禅雅塔还是得从桥下经过,落单反而更容易死。对于水平相当的对战,很难想通过源氏、法拉等个人超水平发挥来牵制两名以上防守方英雄足够长的时间,让剩下 5 名队友获得足够长时间的人数优势从而压制并控制桥洞。

A – B (城门)运载路线

运车路线全程都很狭窄,并且左右优势高地众多。进攻方由于必须推车,并没有很好的分支路线选择,顶多就是源氏法拉略微脱离人群打开视野,而防守方却有多个选择,甚至可以集体绕后或居高临下强冲。尤其是 A B 点之间那段路,对进攻方简直是恶梦。前半段几十米路有三个弯曲,暴露在至少五六处不同高地的火力覆盖范围内,防守方登上城墙以后不用绕直接就可以突袭进攻方后方辅助位。同时路线狭窄,查莉娅大招、小美大招甚至温斯顿大招都可以分割整条道路。这前半段是少有的防守方可以在任何位置都可以主动寻求开团的地图版块了,比好莱坞 A B 段还强硬,至少好莱坞两个房顶只能占一个。

后半段则是奈何桥,这桥防守方重生路线比进攻方略短,交换人头会积累微弱的优势,同时左侧的城墙高点还能发挥作用,进攻方要上城墙需要从左边绕一大圈,而防守方只是顺路。进攻方需要尽量避免在桥上长时间作战,被迫作战也要尽量以攻城锤为掩体,否则全场最佳大概就会变成法拉的了。对鱼塘局而言,有大门遮挡视野,防守方重生英雄很难中途被截,必然可以赶到战场,而进攻方重生英雄则要面临整条路线上的任何可能位置的埋伏。

这段路线尽管非常短,差不多是所有地图中最短的一条,但同时也是最易守难攻的一段了。

B – 运载终点

这段防守方的优势没有前面的大了但依然存在,一是拐弯左侧小道利于防守方,二是依然狭窄的道路对于防守续命英雄(温斯顿、美、D.VA 等)也都是利好。但其实在我看来,这段路线对进攻方的最大压力已经不在于地形,而在于经过前两段路以后,捉襟见肘的剩余时间了。

总结

主路小,几个关键关卡点没有分路,繁多的支线和立体地形,使得防守方的优势极其巨大。由于鱼塘局和高端局在攻防配合上的天然差距,这图会成为鱼塘进攻方的恶梦,同时对于高端局而言,也因为存在过大的不确定性,导致 Banpick 中被选出的可能性较小。

源氏、法拉等立体机动性英雄和狂鼠等封路英雄优势较大,而猎空却受限明显,黑百合在 A 点桥门和 B 点奈何桥的争夺中可以当奇兵一用(狙掉两个,强冲一波)。

由于制作地图工程量巨大,后期也很难修改,所以猜测暴雪会在日后加快推车速度以平衡一下鱼塘进攻方的劣势,不会修改地图本身。但由于高端局和比赛是『一次团战胜利推一段』的模式,较短的路线和加快的车速又会变成进攻方的优势。就是说,这个地图放大了配合推进的重要性,导致鱼塘和比赛的攻防胜率更加割裂。不知道暴雪之后会怎么处理,会不会出现加快 AB 段车速减慢 BC 段车速的奇葩情况。拭目以待。

1120 update:『下载时自动根据文本内容重命名』终于实现了。不能启用的请关闭 adblock 再试。
1006 update: 添加 Ctrl+Enter 功能:提交当前条目,并自动跳到下一条编辑面板,方便连续编辑。
0522 update: 在提交编辑文本内容时,会再播放一遍该音频,方便检查。
12.11 update: 试图添加『下载时自动根据文本内容重命名』功能 失败了。原因是浏览器限制,跨域 a 连接中的 download 属性会被忽略。请勿再提类似需求。
11.20 update: 请勿随意改动英雄标签,目前数据已经是通过客户端导入的正确数据了,除非你有非常确实的理由。比如为对话语音添加对话另一方是可以的,但猜测就不必了。


感谢您的热心贡献。

如果确信某个条目有错误或需要补充,请直接在页面上修改。

由于音频资源是托管在国外的免费空间上的,因此 移动宽带 国内部分品牌宽带 可能 无法访问 ,请自行准备梯子。

这篇规则说明并规范守望先锋在线音频小站 http://ow.thnuclub.com 条目编辑时的格式。这份规则不是强制的,但相同的格式有助于更好地回馈大家的贡献。

基本格式:

【黑百合】(对猎空)哦呵呵,似乎我们这次要合作了。
【地图】【好莱坞】【导演】还要多久?真希望这玩艺还有轮胎。
【源氏】竜神の剣を喰らえ(尝我龙神剑)(技能演示语音)

细则:

1. 发出语音的英雄名、地图名、特有元素名称,使用黑色方括号【】括起,放在条目开头。如果有多个元素则从大类到小类排列。

2. 并未在语音内出现,但有助于说明场景的,比如对话另一方,触发条件等,使用圆括号()括起,放在正文文本

3. 并未在语音内出现,但有助于解释语义的,比如解释台词梗等,使用圆括号()括起,放在正文文本最后

4. 非中、英文语音(岛田兄弟日文、查莉娅俄文、D.Va 韩文、堡垒蜂鸣器文等),有能力可以使用该语言原文,并使用括号配以中文翻译,放在紧邻正文文本。无法写出原文的,用中文或(中文)。英语文本可以不配中译。

5. 使用规范的英雄、地图、元素名称:莱因哈特 大锤 、查莉娅 毛妹 、路霸 ,尽管小站有少量繁体与海外访客,但考虑到最大用户群和称呼一致性,还是以简体中文客户端的英雄名称为准。

简体中文版英雄名称:堡垒,D.VA,源氏,半藏,狂鼠,卢西奥,麦克雷,美,天使,法老之鹰,死神,莱因哈特,路霸,士兵:76,秩序之光,托比昂,猎空,黑白合,温斯顿,查莉雅,禅雅塔,安娜,黑影,奥丽莎,莫伊拉。

6. 优先贡献尚未有内容的条目,越往后翻新条目越多。尽量保证听译的准确,不确定的使用三个问号『???/???』标记。

7. 即使如此,您只要愿意贡献,依然可以无视以上规则。『黑百合 – (法语听不出来)』同样可以接受,留待别人去改进。

标签:

“标签”功能已经上线,齿轮面板是编辑音频标签,顶部面板是筛选标签。请留言建议,改进标签分类。

无标题编辑语音标签

根据标签筛选根据标签筛选

尽管已经导入了一部分标签数据,但目前数据依然较少,需要您的鼎力相助。关于标签的编辑细则:

1. 不确定的内容尽量都勾选。例如听出是女声,但不确定是猎空、D.Va 或者美的声音,则三位都勾选上,以增大曝光机率,更快地修正结果。此条部分废弃,现在涉及英雄的语音已通过拆解客户端导入了正确数据,不必猜测是哪位英雄了。尽管如此,技能音效、环境音效等依然需要通过猜测解决。
此条已废弃。

2. 对话的双方英雄都勾选上。由客户端拆解只能得到发出语音的英雄名,与其对话的另一名英雄,需要手动钩选。

3. 语言数据来自于客户端本身(中英),尽管如此,对于源氏、半藏、小美等英雄,可以按实际情况添加标签,但请勿改动旧标签。

4. 没有数据时,不会得到筛选结果,所以请勿询问为何搜索结果比标签筛选结果多。

5. 堡垒的语音是不分语言的,中英完全相同。

===============================================

由于音频内容增多,单个包的体积已经超出 github 限制,故不得不拆分为 256 个包了。

打包下载地址,随版本更新,请使用批量下载工具:

https://codeload.github.com/k6i/00/zip/gh-pages
https://codeload.github.com/k6i/10/zip/gh-pages
https://codeload.github.com/k6i/20/zip/gh-pages
https://codeload.github.com/k6i/30/zip/gh-pages
https://codeload.github.com/k6i/40/zip/gh-pages
https://codeload.github.com/k6i/50/zip/gh-pages
https://codeload.github.com/k6i/60/zip/gh-pages
https://codeload.github.com/k6i/70/zip/gh-pages
https://codeload.github.com/k6i/80/zip/gh-pages
https://codeload.github.com/k6i/90/zip/gh-pages
https://codeload.github.com/k6i/a0/zip/gh-pages
https://codeload.github.com/k6i/b0/zip/gh-pages
https://codeload.github.com/k6i/c0/zip/gh-pages
https://codeload.github.com/k6i/d0/zip/gh-pages
https://codeload.github.com/k6i/e0/zip/gh-pages
https://codeload.github.com/k6i/f0/zip/gh-pages
https://codeload.github.com/k6i/01/zip/gh-pages
https://codeload.github.com/k6i/11/zip/gh-pages
https://codeload.github.com/k6i/21/zip/gh-pages
https://codeload.github.com/k6i/31/zip/gh-pages
https://codeload.github.com/k6i/41/zip/gh-pages
https://codeload.github.com/k6i/51/zip/gh-pages
https://codeload.github.com/k6i/61/zip/gh-pages
https://codeload.github.com/k6i/71/zip/gh-pages
https://codeload.github.com/k6i/81/zip/gh-pages
https://codeload.github.com/k6i/91/zip/gh-pages
https://codeload.github.com/k6i/a1/zip/gh-pages
https://codeload.github.com/k6i/b1/zip/gh-pages
https://codeload.github.com/k6i/c1/zip/gh-pages
https://codeload.github.com/k6i/d1/zip/gh-pages
https://codeload.github.com/k6i/e1/zip/gh-pages
https://codeload.github.com/k6i/f1/zip/gh-pages
https://codeload.github.com/k6i/02/zip/gh-pages
https://codeload.github.com/k6i/12/zip/gh-pages
https://codeload.github.com/k6i/22/zip/gh-pages
https://codeload.github.com/k6i/32/zip/gh-pages
https://codeload.github.com/k6i/42/zip/gh-pages
https://codeload.github.com/k6i/52/zip/gh-pages
https://codeload.github.com/k6i/62/zip/gh-pages
https://codeload.github.com/k6i/72/zip/gh-pages
https://codeload.github.com/k6i/82/zip/gh-pages
https://codeload.github.com/k6i/92/zip/gh-pages
https://codeload.github.com/k6i/a2/zip/gh-pages
https://codeload.github.com/k6i/b2/zip/gh-pages
https://codeload.github.com/k6i/c2/zip/gh-pages
https://codeload.github.com/k6i/d2/zip/gh-pages
https://codeload.github.com/k6i/e2/zip/gh-pages
https://codeload.github.com/k6i/f2/zip/gh-pages
https://codeload.github.com/k6i/03/zip/gh-pages
https://codeload.github.com/k6i/13/zip/gh-pages
https://codeload.github.com/k6i/23/zip/gh-pages
https://codeload.github.com/k6i/33/zip/gh-pages
https://codeload.github.com/k6i/43/zip/gh-pages
https://codeload.github.com/k6i/53/zip/gh-pages
https://codeload.github.com/k6i/63/zip/gh-pages
https://codeload.github.com/k6i/73/zip/gh-pages
https://codeload.github.com/k6i/83/zip/gh-pages
https://codeload.github.com/k6i/93/zip/gh-pages
https://codeload.github.com/k6i/a3/zip/gh-pages
https://codeload.github.com/k6i/b3/zip/gh-pages
https://codeload.github.com/k6i/c3/zip/gh-pages
https://codeload.github.com/k6i/d3/zip/gh-pages
https://codeload.github.com/k6i/e3/zip/gh-pages
https://codeload.github.com/k6i/f3/zip/gh-pages
https://codeload.github.com/k6i/04/zip/gh-pages
https://codeload.github.com/k6i/14/zip/gh-pages
https://codeload.github.com/k6i/24/zip/gh-pages
https://codeload.github.com/k6i/34/zip/gh-pages
https://codeload.github.com/k6i/44/zip/gh-pages
https://codeload.github.com/k6i/54/zip/gh-pages
https://codeload.github.com/k6i/64/zip/gh-pages
https://codeload.github.com/k6i/74/zip/gh-pages
https://codeload.github.com/k6i/84/zip/gh-pages
https://codeload.github.com/k6i/94/zip/gh-pages
https://codeload.github.com/k6i/a4/zip/gh-pages
https://codeload.github.com/k6i/b4/zip/gh-pages
https://codeload.github.com/k6i/c4/zip/gh-pages
https://codeload.github.com/k6i/d4/zip/gh-pages
https://codeload.github.com/k6i/e4/zip/gh-pages
https://codeload.github.com/k6i/f4/zip/gh-pages
https://codeload.github.com/k6i/05/zip/gh-pages
https://codeload.github.com/k6i/15/zip/gh-pages
https://codeload.github.com/k6i/25/zip/gh-pages
https://codeload.github.com/k6i/35/zip/gh-pages
https://codeload.github.com/k6i/45/zip/gh-pages
https://codeload.github.com/k6i/55/zip/gh-pages
https://codeload.github.com/k6i/65/zip/gh-pages
https://codeload.github.com/k6i/75/zip/gh-pages
https://codeload.github.com/k6i/85/zip/gh-pages
https://codeload.github.com/k6i/95/zip/gh-pages
https://codeload.github.com/k6i/a5/zip/gh-pages
https://codeload.github.com/k6i/b5/zip/gh-pages
https://codeload.github.com/k6i/c5/zip/gh-pages
https://codeload.github.com/k6i/d5/zip/gh-pages
https://codeload.github.com/k6i/e5/zip/gh-pages
https://codeload.github.com/k6i/f5/zip/gh-pages
https://codeload.github.com/k6i/06/zip/gh-pages
https://codeload.github.com/k6i/16/zip/gh-pages
https://codeload.github.com/k6i/26/zip/gh-pages
https://codeload.github.com/k6i/36/zip/gh-pages
https://codeload.github.com/k6i/46/zip/gh-pages
https://codeload.github.com/k6i/56/zip/gh-pages
https://codeload.github.com/k6i/66/zip/gh-pages
https://codeload.github.com/k6i/76/zip/gh-pages
https://codeload.github.com/k6i/86/zip/gh-pages
https://codeload.github.com/k6i/96/zip/gh-pages
https://codeload.github.com/k6i/a6/zip/gh-pages
https://codeload.github.com/k6i/b6/zip/gh-pages
https://codeload.github.com/k6i/c6/zip/gh-pages
https://codeload.github.com/k6i/d6/zip/gh-pages
https://codeload.github.com/k6i/e6/zip/gh-pages
https://codeload.github.com/k6i/f6/zip/gh-pages
https://codeload.github.com/k6i/07/zip/gh-pages
https://codeload.github.com/k6i/17/zip/gh-pages
https://codeload.github.com/k6i/27/zip/gh-pages
https://codeload.github.com/k6i/37/zip/gh-pages
https://codeload.github.com/k6i/47/zip/gh-pages
https://codeload.github.com/k6i/57/zip/gh-pages
https://codeload.github.com/k6i/67/zip/gh-pages
https://codeload.github.com/k6i/77/zip/gh-pages
https://codeload.github.com/k6i/87/zip/gh-pages
https://codeload.github.com/k6i/97/zip/gh-pages
https://codeload.github.com/k6i/a7/zip/gh-pages
https://codeload.github.com/k6i/b7/zip/gh-pages
https://codeload.github.com/k6i/c7/zip/gh-pages
https://codeload.github.com/k6i/d7/zip/gh-pages
https://codeload.github.com/k6i/e7/zip/gh-pages
https://codeload.github.com/k6i/f7/zip/gh-pages
https://codeload.github.com/k6i/08/zip/gh-pages
https://codeload.github.com/k6i/18/zip/gh-pages
https://codeload.github.com/k6i/28/zip/gh-pages
https://codeload.github.com/k6i/38/zip/gh-pages
https://codeload.github.com/k6i/48/zip/gh-pages
https://codeload.github.com/k6i/58/zip/gh-pages
https://codeload.github.com/k6i/68/zip/gh-pages
https://codeload.github.com/k6i/78/zip/gh-pages
https://codeload.github.com/k6i/88/zip/gh-pages
https://codeload.github.com/k6i/98/zip/gh-pages
https://codeload.github.com/k6i/a8/zip/gh-pages
https://codeload.github.com/k6i/b8/zip/gh-pages
https://codeload.github.com/k6i/c8/zip/gh-pages
https://codeload.github.com/k6i/d8/zip/gh-pages
https://codeload.github.com/k6i/e8/zip/gh-pages
https://codeload.github.com/k6i/f8/zip/gh-pages
https://codeload.github.com/k6i/09/zip/gh-pages
https://codeload.github.com/k6i/19/zip/gh-pages
https://codeload.github.com/k6i/29/zip/gh-pages
https://codeload.github.com/k6i/39/zip/gh-pages
https://codeload.github.com/k6i/49/zip/gh-pages
https://codeload.github.com/k6i/59/zip/gh-pages
https://codeload.github.com/k6i/69/zip/gh-pages
https://codeload.github.com/k6i/79/zip/gh-pages
https://codeload.github.com/k6i/89/zip/gh-pages
https://codeload.github.com/k6i/99/zip/gh-pages
https://codeload.github.com/k6i/a9/zip/gh-pages
https://codeload.github.com/k6i/b9/zip/gh-pages
https://codeload.github.com/k6i/c9/zip/gh-pages
https://codeload.github.com/k6i/d9/zip/gh-pages
https://codeload.github.com/k6i/e9/zip/gh-pages
https://codeload.github.com/k6i/f9/zip/gh-pages
https://codeload.github.com/k6i/0a/zip/gh-pages
https://codeload.github.com/k6i/1a/zip/gh-pages
https://codeload.github.com/k6i/2a/zip/gh-pages
https://codeload.github.com/k6i/3a/zip/gh-pages
https://codeload.github.com/k6i/4a/zip/gh-pages
https://codeload.github.com/k6i/5a/zip/gh-pages
https://codeload.github.com/k6i/6a/zip/gh-pages
https://codeload.github.com/k6i/7a/zip/gh-pages
https://codeload.github.com/k6i/8a/zip/gh-pages
https://codeload.github.com/k6i/9a/zip/gh-pages
https://codeload.github.com/k6i/aa/zip/gh-pages
https://codeload.github.com/k6i/ba/zip/gh-pages
https://codeload.github.com/k6i/ca/zip/gh-pages
https://codeload.github.com/k6i/da/zip/gh-pages
https://codeload.github.com/k6i/ea/zip/gh-pages
https://codeload.github.com/k6i/fa/zip/gh-pages
https://codeload.github.com/k6i/0b/zip/gh-pages
https://codeload.github.com/k6i/1b/zip/gh-pages
https://codeload.github.com/k6i/2b/zip/gh-pages
https://codeload.github.com/k6i/3b/zip/gh-pages
https://codeload.github.com/k6i/4b/zip/gh-pages
https://codeload.github.com/k6i/5b/zip/gh-pages
https://codeload.github.com/k6i/6b/zip/gh-pages
https://codeload.github.com/k6i/7b/zip/gh-pages
https://codeload.github.com/k6i/8b/zip/gh-pages
https://codeload.github.com/k6i/9b/zip/gh-pages
https://codeload.github.com/k6i/ab/zip/gh-pages
https://codeload.github.com/k6i/bb/zip/gh-pages
https://codeload.github.com/k6i/cb/zip/gh-pages
https://codeload.github.com/k6i/db/zip/gh-pages
https://codeload.github.com/k6i/eb/zip/gh-pages
https://codeload.github.com/k6i/fb/zip/gh-pages
https://codeload.github.com/k6i/0c/zip/gh-pages
https://codeload.github.com/k6i/1c/zip/gh-pages
https://codeload.github.com/k6i/2c/zip/gh-pages
https://codeload.github.com/k6i/3c/zip/gh-pages
https://codeload.github.com/k6i/4c/zip/gh-pages
https://codeload.github.com/k6i/5c/zip/gh-pages
https://codeload.github.com/k6i/6c/zip/gh-pages
https://codeload.github.com/k6i/7c/zip/gh-pages
https://codeload.github.com/k6i/8c/zip/gh-pages
https://codeload.github.com/k6i/9c/zip/gh-pages
https://codeload.github.com/k6i/ac/zip/gh-pages
https://codeload.github.com/k6i/bc/zip/gh-pages
https://codeload.github.com/k6i/cc/zip/gh-pages
https://codeload.github.com/k6i/dc/zip/gh-pages
https://codeload.github.com/k6i/ec/zip/gh-pages
https://codeload.github.com/k6i/fc/zip/gh-pages
https://codeload.github.com/k6i/0d/zip/gh-pages
https://codeload.github.com/k6i/1d/zip/gh-pages
https://codeload.github.com/k6i/2d/zip/gh-pages
https://codeload.github.com/k6i/3d/zip/gh-pages
https://codeload.github.com/k6i/4d/zip/gh-pages
https://codeload.github.com/k6i/5d/zip/gh-pages
https://codeload.github.com/k6i/6d/zip/gh-pages
https://codeload.github.com/k6i/7d/zip/gh-pages
https://codeload.github.com/k6i/8d/zip/gh-pages
https://codeload.github.com/k6i/9d/zip/gh-pages
https://codeload.github.com/k6i/ad/zip/gh-pages
https://codeload.github.com/k6i/bd/zip/gh-pages
https://codeload.github.com/k6i/cd/zip/gh-pages
https://codeload.github.com/k6i/dd/zip/gh-pages
https://codeload.github.com/k6i/ed/zip/gh-pages
https://codeload.github.com/k6i/fd/zip/gh-pages
https://codeload.github.com/k6i/0e/zip/gh-pages
https://codeload.github.com/k6i/1e/zip/gh-pages
https://codeload.github.com/k6i/2e/zip/gh-pages
https://codeload.github.com/k6i/3e/zip/gh-pages
https://codeload.github.com/k6i/4e/zip/gh-pages
https://codeload.github.com/k6i/5e/zip/gh-pages
https://codeload.github.com/k6i/6e/zip/gh-pages
https://codeload.github.com/k6i/7e/zip/gh-pages
https://codeload.github.com/k6i/8e/zip/gh-pages
https://codeload.github.com/k6i/9e/zip/gh-pages
https://codeload.github.com/k6i/ae/zip/gh-pages
https://codeload.github.com/k6i/be/zip/gh-pages
https://codeload.github.com/k6i/ce/zip/gh-pages
https://codeload.github.com/k6i/de/zip/gh-pages
https://codeload.github.com/k6i/ee/zip/gh-pages
https://codeload.github.com/k6i/fe/zip/gh-pages
https://codeload.github.com/k6i/0f/zip/gh-pages
https://codeload.github.com/k6i/1f/zip/gh-pages
https://codeload.github.com/k6i/2f/zip/gh-pages
https://codeload.github.com/k6i/3f/zip/gh-pages
https://codeload.github.com/k6i/4f/zip/gh-pages
https://codeload.github.com/k6i/5f/zip/gh-pages
https://codeload.github.com/k6i/6f/zip/gh-pages
https://codeload.github.com/k6i/7f/zip/gh-pages
https://codeload.github.com/k6i/8f/zip/gh-pages
https://codeload.github.com/k6i/9f/zip/gh-pages
https://codeload.github.com/k6i/af/zip/gh-pages
https://codeload.github.com/k6i/bf/zip/gh-pages
https://codeload.github.com/k6i/cf/zip/gh-pages
https://codeload.github.com/k6i/df/zip/gh-pages
https://codeload.github.com/k6i/ef/zip/gh-pages
https://codeload.github.com/k6i/ff/zip/gh-pages

计算机中没有随机数。

本文完。


小游戏里的小随机

如果做个打地鼠小游戏,要让地鼠从 9 个小洞里『随机』出现,不能挨个洞出来。简单办法是给九个小洞编上号 0-8,然后令 Holen+1 = (4 * Holen + 4) mod 9。也就是乘4加4,然后除 9 取余数。游戏开始,我们希望地鼠第一次从中间的洞,也就是4号洞里出来。所以 Hole1=4。得到的结果是:

1
4,2,3,7,5,6,1,8,0,4,2,3,7,5,6,1,8,0,4,2,3,7,5,6,1,8,0,……

也就是地鼠会这样出现:

mouse

看起来很规律,记一记就记住了。但把上面公式的几个常数放大,变成 Xn+1 = (1103515245 * Xn + 12345) mod 231,序列就变成了:

1
4,119106029,1583775792,1104372736,1199162368,546350080,1200489472,1420813312,2119975936,33629184,192185400,1547013152,339646976,1301529152,177825024,1578512704,703899648,1482405888,1629325312,151197696,1470195776,1593643776,1066092288,1743743744,1972027136,205882112,964884288,1603859968,320138752,342722112,654956928,……

数字太大,做一下归一化。因为是 mod 231的结果序列,所以归一化也就是除以 231,得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
0.0000000019
0.0554630668
0.7375030741
0.5142636299
0.5584034920
0.2544140816
0.5590214729
0.6616177559
0.9871907234
0.0156598091
0.0894933008
0.7203841358
0.1581604481
0.6060717404
0.0828062296
0.7350522578
0.3277788162
0.6902990341
0.7587137222
0.0704069138
0.6846132576
0.7420982122
0.4964379072
0.8119939566
0.9182966948
0.0958713293
0.4493092597
0.7468554974
0.1490762234
0.1595924199
0.3049880862
…………

我们再次把这个序列处理回地鼠出现的序列,Floor(Xn*9),得到:

1
0,0,6,4,5,2,5,5,8,0,0,6,1,5,0,6,2,6,6,0,6,6,4,7,8,0,4,6,1,1,2,……

来 1000 个:

1
0,0,6,4,5,2,5,5,8,0,0,6,1,5,0,6,2,6,6,0,6,6,4,7,8,0,4,6,1,1,2,6,4,1,2,0,2,3,1,1,6,6,4,6,4,5,2,3,8,0,7,8,4,3,0,4,1,1,0,4,3,5,4,6,8,8,1,2,5,6,5,8,2,4,8,1,2,6,3,1,3,7,8,8,4,2,8,7,8,4,5,3,6,3,3,6,8,0,1,1,4,8,0,5,3,5,7,5,4,4,0,7,5,3,1,4,1,2,3,5,8,7,0,5,6,5,0,6,0,4,3,1,7,4,0,3,4,6,5,2,5,5,8,1,2,7,7,3,8,0,4,8,5,3,6,7,6,1,5,2,5,0,1,0,6,5,3,6,0,7,1,3,0,1,2,0,2,4,6,0,2,1,5,6,2,1,0,0,0,0,7,2,4,0,1,6,2,4,3,4,1,5,1,6,7,3,7,8,3,3,3,3,6,1,1,4,0,3,7,5,0,0,3,7,7,7,5,4,3,8,2,2,2,7,7,2,6,1,2,2,6,3,8,8,3,3,0,2,2,3,6,1,4,6,1,8,1,5,7,1,7,6,5,0,6,4,2,0,6,0,0,8,7,6,8,0,1,2,5,5,1,1,3,7,7,0,3,2,4,5,1,2,1,0,8,1,6,6,4,5,5,1,0,1,5,5,6,5,3,5,7,5,0,2,7,8,7,6,5,6,7,0,1,0,3,4,1,4,4,2,3,7,8,1,5,4,1,8,8,6,5,0,8,1,6,4,7,6,4,1,8,5,3,5,8,6,4,7,3,3,0,7,2,3,3,8,0,5,3,6,6,3,0,1,4,7,4,0,1,5,0,1,4,3,0,4,0,0,1,0,5,4,4,5,4,3,6,4,8,5,7,6,7,4,0,8,4,2,7,8,6,6,0,4,4,6,6,6,4,2,3,8,2,1,6,8,5,3,6,4,3,7,3,4,5,4,8,6,1,5,0,4,1,7,6,5,7,3,5,6,5,7,5,0,4,0,1,7,0,5,6,3,0,8,7,6,3,3,3,8,1,3,4,8,1,4,7,3,3,3,1,6,0,4,8,8,3,7,5,7,3,8,3,3,7,2,7,7,1,3,7,2,6,7,1,1,7,4,4,8,0,7,2,6,7,6,0,7,7,1,8,2,3,4,7,3,3,4,3,0,0,7,5,1,8,2,3,1,0,1,4,7,2,0,0,7,8,5,3,1,0,3,8,5,7,8,5,1,5,3,7,3,6,0,8,8,7,8,8,4,7,1,0,6,5,6,4,5,0,4,7,0,8,5,8,8,5,4,3,8,5,4,3,8,6,6,4,6,3,3,3,1,6,3,0,4,7,7,2,2,2,7,1,3,2,0,4,2,2,2,1,2,2,8,2,4,7,8,7,1,2,6,8,0,7,3,1,0,7,0,0,8,6,1,0,0,4,1,0,5,4,2,8,8,2,8,7,7,0,3,3,8,8,2,4,1,3,6,1,1,5,3,6,8,3,3,3,5,4,4,8,4,6,1,6,2,8,1,1,3,0,0,5,0,0,2,2,8,8,8,8,3,7,1,0,0,5,0,7,7,7,7,4,3,2,6,8,7,6,7,0,4,8,7,2,2,7,6,3,5,2,8,8,5,6,8,2,0,8,3,7,0,4,5,4,4,0,5,3,5,4,1,7,3,7,5,8,4,6,0,6,2,1,4,6,7,8,0,3,0,5,5,3,2,5,2,6,4,7,0,6,5,2,1,0,4,1,5,3,4,6,8,0,1,5,5,1,1,1,3,8,3,2,7,4,5,4,3,1,4,0,8,3,5,6,7,4,5,8,2,6,2,1,4,6,3,6,3,7,2,5,8,2,5,8,1,3,3,5,7,8,5,4,6,0,7,3,3,4,8,3,2,4,0,4,4,2,7,6,2,1,0,4,0,1,8,3,2,8,0,5,2,1,1,1,2,1,7,4,1,1,4,1,8,7,5,6,2,5,3,4,6,2,8,3,7,6,7,5,2,1,0,7,1,8,8,5,5,7,2,5,6,4,4,2,5,2,0,2,5,6,8,2,4,6,3,7,4,5,0,0,4,1,2,1,7,1,2,5,3,3,0,3,8,7,6,0,2,5,5,6,4,2,0,8,7,2,0,2,5,0,4,5,4,8,2,7,6,4,1,7,7,6,5,1,6,8,0,0,1,0,4,6,3,0,7,2,7,5,1,0,5,5,2,6,0,7,0,4,0,……

这个已经足够『随机』了,发现不了什么明显的规律,统计上也符合平均分布。要说规律也是有的,就像之前 mod 9 于是出现了 9 个一循环一样。在把常数增大以后,这个序列的循环也变成了 231

因为打地鼠游戏中的『随机』的要求,只是无法让玩家找到并掌握规律。任何一个玩家都不可能打上 231 只地鼠,所以这个算法生成的『随机』已经足够了,截取其中一段完全符合打地鼠的要求。

事实上, fn+1 = (a * fn+b) mod c 正是一种很传统的计算机随机数生成法,称之为线性同余法,是使用最广泛也是最古典的随机数生成算法之一。随着时代发展和实际需求不断加深,更加严格的算法被不断发展出来,但线性同余由于算法简单,计算性能高效而又能满足很大一部分一般需求,依然是大部分编程语言 rand() 函数的默认实现方式。从公式可知这些随机数并不是真正的随机,总是会在 c 的范围内循环出现。当

1
2
3
b 和 c 互素
a-1 可被所有 c 的质因数整除
如果 c 是 4 的整数倍,则 a-1 也是 4 的整数倍

时,输出序列的周期为 c。只要保证 c 足够大,那么一般情况下是不会遇到周期性重复的。

事实上 a=1103515245, b=12345, c=231 正是C语言标准函数 rand() 所使用的几个常数。rand() 函数包含于 stdlib.h 头文件库中。

种子

从算法可知,假如 X0 不变,那么每一次执行程序,得到的“随机数据”其实都是一样的。执行两遍程序,得到完全样同的数据,重开一次游戏,NPC 的行为一模一样,这感觉其实并不怎么好。但从数学的角度上看,既然算法是一定的,那么每一步得到的数据也必然是确定的,如果初始值相同,那么后续的值也一定是相同的。甚至即使一个序列中断了,只要后续重新设定的种子值与中断前的最后一个值一致,那么这个序列就可以原样继续下去。

换个角度看,只要用不同的初始值,我们就能得到不同的结果。

这一初始值,被称为算法的『种子』(Seed)。最常用的种子数据大概就是使用系统的当前时间了,这是一个简单易用,必然存在,而且还时时刻刻在变化的值。可以让每时每刻产生的随机序列都有所不同。稍复杂一些的有 CPU 当前温度,用户鼠标的移动轨迹,键盘的击键速度等,属于不可预测的 Seed。

『种子』这个词汇甚至不光是随机数据生成算法里有,加密解密算法、身份识别、图像算法等都有这一词汇的出现(当然下载界也有这个词)。对加密算法而言,种子数据往往是高度保密或者完全无法复现的了。前面说的鼠标键盘数据即属于此类。于是围绕种子值往往也会产生许多巧妙的奇思秒想与攻防破解,后文再述。

随机与伪随机

不光是线性同余,计算机里一切随机数生成方法实际上都是通过一定的算法生成的,区别只是在于算法复杂不复杂,种子的要求高不高,得出来的 结果能不能满足实际场景的需要。这些通过公式计算得到的“随机数”,有一个特定的名称『伪随机』(Pseudorandomness)。即,看起来像是随机的,实际却不是。但看起来随机往往就足够了。

在有些场景里,这种通过计算机生成的伪随机算法会变得无法符合真实需求。

  1. 大规模的理化生模拟实验,伪随机算法在统计上的瑕疵 可能 会影响到模拟实验的结果数据。

    ——由于实验可能会用到海量的随机数据,部分伪随机算法的规律性或者其它统计上的瑕疵正好撞上实验的检查项,就会导致实验结果的异常。

  2. 程序员开年会,了解了随机数原理的程序员们纷纷表示不能被一个确定的算法影响了自己的『强运』。

    ——由于对年会奖品抽奖程序的不满导致年会现场变成 Code Review 大会的事情简直随处可闻。

  3. 赌场这种胜负直接关系到利益的地方

    ——关于伪随机算法缺陷导致巨额损失的情况后文细讲。

  4. 涉及到窃听与加密、远程控制肉机等可能造成反复利用的情况

    ——往往破解出一种加密方法,就可以进入大量的服务器,产生巨大的利益。

这时人们会更倾向于使用常识中认为的『真随机数』,例如放射性衰变、电子设备的热噪音、宇宙射线的触发时间等等。比如专门提供这类服务的 random.org 网站,声称是通过测量大气噪音(Atmospheric Noise)获得的随机数据。相比于受到算法限制的数据,毕竟这些数据感觉更“随机”一些。

当然,使用环境数据,则会受到测量传感器采样率的限制,数据的生成速度会受到影响。同时,很多自然环境数据也是连续渐变,或者服从一些确定的分布,随机性可能没有预想的高。比如 CPU 温度之类,在一定时间内的变化总是一条较为连续平滑的曲线。

所以更务实的办法是混用两种情况,即使用一套设计良好的随机数生成算法,了解算法的适用场景,避开算法缺陷,同时使用环境数据作为 Seed。初始值『真随机』后,后续尽管是伪随机但也拥有了更大的不可预测性,在大量重复实验中会表现出来更多的随机性,也就有了更广泛的使用场景。

“更随机”与“不那么随机”

对『随机程度』概念的理解多少有点凭感觉。尽管数学上对随机和随机程度是有严格定义的,但引用《信息简史》里提及的,香农提出的例子,大概更容易让人理解『随机程度』的意思:

·零阶近似一一完全随机的字符,其中不存在结构或依赖:
XFOML RXKHRJFFJUJ ZLPWCFWKCYJ FFJEYVKCQSGHYD
QPAAMKBZAACIBZLHJQD.

·一阶近似一一每个字符与其他字符不存在依赖关系,各自的出现频率取在英语中的出现频率:字母 e 和 t 出现得较多,而 z 和 j 较少,且单词长度看起来也较接近现实。
OCRO HLI RGWR NIMIELWIS EU LL NBNESEBYA TH EEI ALHENHTTPA OOBTTVA NAH BRL.

·二阶近似一一不仅单个字母,双字母组合的出现频率也符合英语的情况。
(香农从密码破解者所用的表格中,找到了所需的统计数据。英语中最常出现的双字母组合是 th ,大致每千个单词出现 168 次,紧跟其后的是 he, an, re, 和 er 还有相当数量的双字母组合的出现频率为零。)
ON IE ANTSOUTINYS ARE T INCTORE ST BE S DEAMY ACHIN DILONASIVE TUCOOWE AT TEASONARE FUSO TIZIN ANDY TOBE SEACE CTISBE.

三阶近似一一三字母组合也符合英语的情况。
IN NO 1ST LAT HEY CRATICT FROURE BIRS GROCID PONDENOME OF DEMONSTURES OF THE REPTAGIN IS REGOACTIONA OF CRE.

一阶单词近似
REPRESENTING AND SPEEDILY IS AN GOOD APT OR COME CAN DIFFERENT NATURAL HERE HE THE A IN CAME THE TO OF TO EXPERT GRAY COME TO FURNISHES THE LINE MESSAGE HAD BE THESE

二阶单词近似一一双单词组合以英语中期望的频率出现,所以不会出现上例中 “A IN” 或 “TO OF” 的情况。
THE HEAD AND IN FRONTAL ATTACK ON AN ENGLISH WRITER THAT THE CHARACTER OF THIS POINT THEREFORE ANOTHER METHOD FOR THE LETTERS THAT THE TIME OF WHO EVER TOLD THE PROBLEM FOR AN UNEXPECTED

衡量随机性的方法就是香农同学提出的信息熵概念。在这里没有必要展开论述计算方法,我们使用另一个例子来简单说明随机程度:

这是一个来自 知乎 的巧妙方法,尽管局限性很大,但却一目了然:

random

对于通过算法生成的随机数序列而言,在整个序列中多少会存在一定程度的『局部结构』。『结构』的存在和反复出现,就表明这一序列的随机程度较差。结构越多越密,随机度越差。上面两个例子都展示了这种『结构』的存在。结构是可以预测的,可预测的越多,随机性就越少。赌徒们大概更容易理解这点吧。

生成方法与评价方法

线性同余法

线性同余法的公式非常简单,如果已知了足够长的结果序列以后,三个参数 a, b, c 也很容易反推出来。从数学上讲,任何一个可以被计算机运行的算法都必然有有限大的取值范围,既然范围有限,到最后必然会出现周期性循环。尽管如此,231(大约21亿)的循环周期也小了一点。尽管没人会去打 21 亿只地鼠,但是 21 亿粒子碰撞,21 亿次装备掉落等等都是很有可能出现的。线性同余法的优点在于高效简洁易于实现,能满足常规需求,但人们还是需要更复杂更随机的算法。

Mersenne Twister

维基百科 上有比较详细的论述。它的循环周期为 219937 − 1,这是个梅森数所以才叫“梅森旋转”,和梅森本人大概是没啥关系。算法优点一是循环周期大,二是可以通过一些比较严苛的随机数测试,三是可以实现 1 ≤ k ≤ 623 范围内的 32 位精度 k-分布。

缺点一是速度较慢,但在现有的计算机性能下常规运算不是太大问题。二是在一些特定的初始值下,输出的序列会有大量相似模式。也就是会出现初始值相似而输出序列大量相似的情况,这个比较要命。另外,它通不过 TestU01 等少数随机测试。

Multiply with carry
MWC 是 George Marsaglia 提出的方法,优点在于速度快周期特别大,大约有 260 到 22000000。缺点维基上没写,我猜测大概是结构较为明显,不适合的场景较多吧。OpenCV 使用了 MWC 作为随机数生成器,在图像领域,速度和周期确实是最重要的,相比之下,结构导致的缺陷并不重要且容易规避。

其它
维基百科上有专门的目录页提到了伪随机数生成器:https://en.wikipedia.org/wiki/Category:Pseudorandom_number_generators,而很显然这并不是全部的随机数生成程序。每一种伪随机算法都有自己的优点和缺点,适用于不同的场景。在一些简单场景下,设计人员也会使用更简化的,可以通过互质齿轮组机械结构实现的随机,比如:

98aed764834fb9165ced3945b6bc87b2_b

算法评价

同时,当然也有各种针对伪随机数测试方法,去检验生成数据的质量(即随机性如何)。原理基本都是评测生成的序列是否符合必要的复杂性要求。这与无损压缩算法反而有某种异曲同工之处,毕竟压缩算法的目的就是寻找规律模式并且使用更短的片段去代替它。几乎所有的随机序列测试都是基于假设检验、广义傅立叶变换和复杂性测试,对一个已经生成的伪随机数序列进行检测,寻找模式,并评测结果。

最简单的理解,如果你用 int(rand(0,255)) 一百万次生成了一个 .bin 文件,扔进 Winzip 里,结果体积压缩了一半,那么这个随机数生成算法就是非常不合格的。这跟上面的点阵图方式很相似。

顺便附上还是从 知乎 同一问题下抄来的四原则:

K1——相同序列的概率非常低
K2——符合统计学的平均性,比如所有数字出现概率应该相同,卡方检验应该能通过,超长游程长度概略应该非常小,自相关应该只有一个尖峰,任何长度的同一数字之后别的数字出现概率应该仍然是相等的等等
K3——不应该能够从一段序列猜测出随机数发生器的工作状态或者下一个随机数
K4——不应该从随机数发生器的状态能猜测出随机数发生器以前的工作状态

作者:DD YY
链接:https://www.zhihu.com/question/20222653/answer/16482344
来源:知乎

说实话这四条只是原则,并不是任何一个实测的测试方法。单一的测试方法也无法涵盖各种可能性,所以一些比较有名的测试,Diehard Test、TestU01 等实际上都是测试包,包含了多种不同的方法,尽可能覆盖测试的方方面面。

回到游戏

计算机程序大部分都服务于确定的目的,不会有随机的成分存在。查字典不可能随机给解释,打字不可能随机出字,做帐目不可能随机变计算结果。那么什么地方需要随机?最常见的也就是游戏了,包括电子赌博。所以再回来说说游戏中的随机。

『乱步』

圆桌武士(Knight of the Round) 是个当年非常受欢迎的街机游戏,应该也是不少人的童年回忆。

knights001

这款游戏后来被有心人研究出来被发现者命名为『乱步』的方法,类似以下这些规则:

1.红色小兵(不戴头盔的,血少)
a.砍开箱子出 800 分宝箱,他在地上滚动的时候,砍开 800 分,出魔杖。
b.屏幕上如果有两个桶,先不开桶,等他在地上滚的时候,放血放死他,然后开另一个桶,再开 800 分的桶,再开 800 分,出魔杖。
2.绿色小兵(戴头盔的)
a.先吸引他跑动(不攻击),如果他马上又跑动攻击的话,出刀时放血放倒但不要放死他,马上去开箱子。等他站起来踹气的时候,开 400 分或蔬菜盘,出地震法球(这种方法不能出杖,up)。
b.在他在地上滚动时,放血放死他,然后开一个箱子,开里面的分或血,再开 800 箱子,开 800 分,出魔杖。
3.胖子(Fatman)
a.走 C 步时( C 形状的步子,步子很小也很快,就那么一下),放血放倒他,马上开箱子,在他起来踹气时开800分,出魔杖。
b.站在他前方等他冲跑过来攻击你,他冲的时候你马上站在他斜下的方向,他如果走向下走一步(只是1步),放血放死他,然后开两个箱子,再砍 800 分,出魔杖。
4.大剑(Swordman)
a.同样是观察它走 C 步,放血放倒它,马上砍 800 箱子,等它站起来就开 800 分,出魔杖。
…………

乱步的实质就是随机数规律被人掌握了。

受限于街机主板不高的性能,游戏开发使用了比较简单的一个随机数发生器,实际的重复周期很小。同时还将宝物掉落、敌兵行动、主角行动等一系列需要随机的行为,全放在同一个随机数序列中。每一个需要随机的行为,都让这个序列往后走一格,一轮序列走完再从头开始第二轮。

也是因此,玩家可以通过反复的的跳跃、放血大招等动作,来『快进』掉随机数序列中的若干位数字,到需要的位置,再劈砍宝箱,以获得指定的需要的宝物。如下图,当随机序列进行到 03 时,游戏中某个小兵根据该数字进行了冲锋动作,使用掉 03 这个数值。序列进行到 1C,被玩家用连续两次跳跃消耗掉两个随机数,使得随机序列当前值变为 0F。而 0F 对应于掉宝则为『魔杖』。

即,用户根据一些游戏内的特别现象,通过自己的操作控制了掉宝内容。

ranbu

可是主角行动是个受玩家操控的行为,并不是一个需要随机的东西,为什么还会在随机序列中并影响到掉宝结果?

有两个原因。一是为了让画面更富有表现力,游戏主角的动作会有多种画面表现。同样是跳,可以表现为前空翻跳,直立跳。同样是转身,一种先转头,一种先迈步,等等。而另一个原因仅仅是为了让序列更随机。没错,就像前文写过的那样,使用键盘击键、鼠标轨迹等算法外部的影响因素,可以让破解者更难以发现规律。当然,也可能两个原因都有。

我猜测圆桌武士这个游戏就是第二类情况。由于玩家行动,尤其是多人游戏,多位玩家同时行动时,玩家行为的不可预测性甚至比有限机能下内部的随机数发生器更有效,这可以避免简单地被玩家发现『如果做了 A 接下来一定是B』的规律,也避免了敌人行动永远一致的囧境。事实上在它的游戏生命周期内,几乎是整个街机的商业周期内,都运作得很好。直到电脑模拟器时代,才由几个多年孜孜不倦研究的玩家发现并完善了这份随机表。

看起来,似乎让玩家动作可以影响随机序列是个坏主意,若玩家掌握规律,岂不是就可以通过自身动作来影响游戏掉宝等结果了么?但事实并非如此。『并非如此』不是说不影响结果,而是说在玩家动作里设置锚点以影响随机数序列,进而影响掉宝,并不是一个坏主意。

因为在有限的机能和简单的算法下,规律始终是容易被发现的(参考第一节的打地鼠)。假如没有玩家动作影响,规律性的掉宝会让玩家非常容易就总结出形如『第二个箱子不打则第五个箱子必出 +100% 生命大血包』的结论,这会极大缩短游戏寿命。而将玩家动作引入后,即使是同一个玩家玩同一个游戏,两次行动也往往并不一致,于是相同游戏进展也就可能掉出各种不同的宝物了。另外,因为玩家并不清楚哪些动作被埋入了影响因子,因此也很难一开始就针对性的调整动作来尝试『探宝』。

在引入玩家动作影响因素以后,什么时候规律才能被发现呢?一是某位玩家熟练到全套过关动作几乎一致时,他才容易发现掉宝似乎也表现出了某种规律性,从而开始探索对掉宝率的控制。这大概就是上面乱步表的由来。另一种是在游戏起始阶段,玩家的行为并不复杂,正好某个掉率也设置较高容易被随机到时,大量玩家的集体行为容易让规律暴露出来。一个第二关开头的水果盘砍出 +2 生命宝物的古老秘籍大概就是这么来的。

而随着硬件性能的不断提升,这种要依靠玩家行为来产生游戏不确定性的情况越来越少了。该方法毕竟是把双刃剑,在可以不用的时候就不必使用了。现在使用任何语言,都有足够多现成的算法可用,并且硬件性能也足以支撑大量的计算了。唯一还需要讲究的大概就是种子值了。

种子对齐

种子和密码正好相反,密码要求前后不变以供核对,而种子则最好每次都不同,因为相同的种子必然产生相同的『随机』序列。如果种子是预先定义好的固定值,服务器每次重启后跑出来的都是相同的序列,开出一样的奖,显然是不合适的。

相同的种子必然产生相同的序列。

发现什么了么?

这意味着,假如你知道某个随机算法的种子,你就拥有了预测能力,精确地知道每一次开奖的结果,就可以批量地安全地赢走电子赌博网站里所有的奖金。甚至就算你不知道种子,发现每次是相同的序列后,也会拿个纸笔记下来吧。在这层意义上,种子值和密码又有了相通之处——不能为人所知。

好办,只要每次种子值都不一样连程序作者自己都不知道不就行了?

——不是这样的。

前文提到最常用的随机种子是时间戳。可是你服务器总要维护重启吧,甚至可能还有自动重启。假如你在凌晨 3:05 重启,直到 3:15 重新开始赌博抽奖。我就知道你这台服务器的赌博程序一定是在 3:05-3:15 之间启动的。假如你真的使用了时间戳作为种子,也一定是在这 10 分种里取的。10 分种,600 秒, 60 万毫秒。大不了把这 60 万个时间全部作为种子挨个跑一遍看哪个能对上你重启后出产的随机序列就好了。能对上的那个序列,对应的时间戳就是你的种子。然后就是原本计划的那样,安全批量地赢走网站里的所有奖金。

好吧,不用时间戳了,使用 CPU 温度吧。同样也不安全,实际运行的机房计算机的 CPU 温度,我们就算它可能是 50℃ ~ 90℃ 吧,传感器温度有限,就算三位小数,那也就是四万种可能情况而已,挨个跑一遍就好了。奖金就到手了。

要知道,计算机不依赖第三方,内部可以提供的变化数据其实就那么几种,而随机算法也只有那么几种。圈定了一个大致的范围以后,『大不了全跑一遍』来反推种子值,其实是非常有效的。相同的随机算法,相同的种子,必然产生相同的序列,于是你就获得了神的预言能力。

那么假如使用外部数据呢,比如前文提到的 random.org。也可以通过 DNS 解析到假网站,提供自己预先准备好的『毒种子』进行攻击。尽管这个方法在 random.org 启用了 SSL 证书以后变得麻烦一些了,但还有更简单的攻击办法,就是让服务器无法访问该网站,直接让你拿不到网站提供的随机种子。

网站总是要经营的,如果服务器一直无法正常运作,损失不比奖金被赢走小。既然种子对齐的目的是赢走奖金,那么当破解难度大到一定程度时,各种攻击勒索就变成收益更高的方式了。

话题似乎跑远了,但其实本文讨论的核心是『计算机随机,够用就好』。当破解的难度大于勒索的成本,基本也就说明在随机算法这一层面,确实够用了。

感觉的随机

『够用』往往还有另一层意思。

我们还是从掉宝率开始,假如一样极品宝物,比如屠龙宝刀 点击就送 在游戏内的掉率为 1%,这意味着大约杀 100 次 boss 会掉一把。作为一个通过反复击杀 boss 获取的装备,掉率 1% 本就是定位于每个人都能获取的高级装备。只要统计一下整个网游的怪物击杀量,就知道那是多么大一个天文数字.如果真要定位为全服稀有,那么掉率必然是几乎接近于零的。

既然是普及型稀有装备,假如一个玩家每天可以杀 boss 一次,那么大约 100 天期望时间也就是三个月左右会掉一把,这也比较符合网游的更新周期。假如有 10 万人玩游戏,那么平均每天大约有 1000 人会获得该武器,通过游戏社区也会刺激其它玩家的游戏动力。随着游戏进程玩家的装备变好,击杀 boss 的速度越来越快,在版本的最后阶段变成例行公事一般,也可以满足游戏运营方对上线率存留率方面的要求。

如果只计算粗放数据,以上一切看起来都很美好。但事实是:

大约有 10,0000 * (1-1%)100 = 36603 名玩家在三个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)150 = 22145 名玩家在五个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)240 = 8963 名玩家在八个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)300 = 4904 名玩家在十个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)365 = 2552 名玩家在一整年以后依然没有获得宝刀;

对于这几千名玩家而言,怀疑游戏开发商虚假承诺数据做假,完全合情合理。在没有附加条件的 1% 随机掉率下,第一天拿到宝物的就有 1000 名玩家,而运气不好刷一整年都不出的有 2552 名玩家。如果我们考虑得更实际一点,不是每个人都天天刷游戏,实际都是新版本刚开火热一点,后面就是有时间上线没时间算了。那我们假设第一周七天天天刷,第一个月每周刷三次,之后丧失新鲜感失望情绪蔓延,每周只上线一次。则:

大约有 10,0000 * (1- 99% 7) = 6793 名玩家在第一周拥有了宝刀,一人多刀没用只算一把,每百人有 6.7 把。
大约有 10,0000 * (1- 99% 16) = 14854 名玩家在第一个月拥有了宝刀,每百人里有 14.8 把宝刀,三周涨了 8.1。
大约有 10,0000 * (1- 99% 24) = 21432 名玩家在三个月内有了宝刀,每百人里有 21.4 把宝刀,两月涨了 6.6。
大约有 10,0000 * (1- 99% 64) = 47440 名玩家在一年内有了宝刀,每百人里有 47.4 把宝刀,九月涨了 28.0。

总比例造成未获得者的不快感蔓延,由于击杀频率的减少也导致存量增长下降。游戏根本撑不到一年。

无标题

假如我们可以付费买 boss 复活再次击杀获得额外一次掉宝机会——就是花钱开宝箱,则需要 chance = 1146 才能使得 10,0000 * (1-1%) chance < 1,也就是说运气最差的哥们需要开一千多个箱才能获得这个 1% 几率的宝物。对他而言,这根本就是千分之一的几率。

不患寡而患不均。不加限制条件的纯随机,对那些一次就出的幸运儿很不错,但整体而言其实给玩家的体验并不怎么好。如果有 10% 的几率,玩家可以容忍 20 次甚至 30 次不出,但如果 1% 的几率,能容忍 200 次的都是凤毛麟角。而前者 20 次不出的几率是 12.15%,后者 200 次不出的几率为 13.40%。越是低的掉率,无限制纯随机给玩家的体验就更不好。究其原因,在于人的感觉与概率的实际表现之间存在差异。人人都不觉得自己是最差的那个倒霉蛋,但总有人会填上这个位置。

有两种截然不同的办法去解决这个问题。

一种是尽量去掉这个位置,比如『积累胜率』。当玩家在一次抽奖不中后,略微提升下一次的中奖几率,如若继续不中,则继续提升,直到 100% 必中为止。例如初始 1% 每次提升 1%,那么再倒霉的人在第 100 次开奖时,面对 100% 的中奖几率也该中了。这样的 100 次才中的倒霉蛋,每十万人里大概会有 9.33×10 -38 人…… 嗯,十万人中最倒霉的倒霉蛋大约也会在第 45-50 次开奖时得到顶级装备,到不了 100 次。魔兽世界里 “幸运币” 的积累机制就是这么做的。

另一种是分割并加大随机性,让这个倒霉蛋的位置被切分到几乎不可感知。暗黑破坏神 3 里的装备随机属性和随机掉落机制是这种方法的例子之一。对于暗黑3而言,一个人物身上有 13 件装备位置,每个位置都有十几种可能的掉落,但最合适的只有一种。而对于每一件特定装备,又是在十几种不同的属性列表中随机获得四到六种属性,每种属性又是在既定的数值上下限区间随机确定某个值。同时对于一个玩家而言,正常游戏在一小时内即可获得十来二十甚至更多的装备。

尽管绝大部分装备都免不了被嫌弃,但通过这种 多装备+多掉落+极大随机 的模式,成功地把倒霉蛋的感觉切分到几乎不可感知了。玩家依然可以在论坛上看见别人的极品装备,但对于自己而言,身上也往往会有几件装备是过得去,勉强能让自己满意的,这已经能极大抚慰倒霉蛋们的感受了。

所以有时候,我们反而需要减少一定程度的随机性,使得它感觉起来更随机。

『We’re making it less random to make it feel more random.』

英文那句不是我说的,是乔布斯说的。

在一个既定音乐列表随机播放时,如果是不加限制的随机,经常会出现同一首曲目连续播放两次甚至更多次的情形。这事实上是很正常的事情,如果每次切歌都是从同一个长度为 n 的列表里选取一首,就意味着每次都有 1/n 的几率选到和上一首相同的歌,于是就重复播放了。直接从播放清单移除听过的歌曲可以解决问题,但就和打乱列表的顺序播放没什么区别了。

根据一些访谈所述,苹果 iPod 的随机播放程序是将不同歌手、不同曲风交错播放,让使用者感觉到每一首歌之间毫无关联,相当的“随机”。

所以你看,科学计算需要的随机,和游戏需要的随机,和播放器需要的随机,和自然界的随机,其实都各有不同。

算法服务于目的。

1. Ubuntu Family Mini ISO

如果网络条件不错的话,安装虚拟机可以使用适用于 Ubuntu 全家族(Kubuntu、Lubuntu、Xubuntu、Edubuntu、Mythubuntu 等)的通用迷你 ISO。32 位或 64 位都有。

https://help.ubuntu.com/community/Installation/MinimalCD (32 位或 64 位)

这个 ISO 只有不到 40MB,在安装过程中会自动从网上下载需要的文件,并在某个步骤让用户选择桌面环境。本来 desktop 版 ISO 在安装时也会上网更新,所以这个 Mini ISO 能节约的时间还是不少的,体积小还易于本地保存。我的网络带宽上限速度大约是 2MB/s,apt 源实际速度大约是 1MB/s 安装完成费时和先用迅雷下完整 ISO 再安装再 update 差不多。顺便还能满足软件洁癖们。

其实就是 Tasksel 整合安装器其实就是 Tasksel 整合安装器

缺点也是有的,界面和 Server 版映像文件一样是纯英文,且有些步骤的默认选择是<No>,不能一路回车到底。所以得能看懂提示。安装步骤倒是和 desktop 版没区别,无非是语言地区、分区、用户名密码这几个选项。

2. 命令行安装 VMWare Tools

使用虚拟机下拉菜单的『安装 VMWare Tools』只会自动载入 VMTools 的 ISO 文件,需要自行解压 VMTools 安装包。

1
2
3
4
5
6
cd ~ --当前目录
sudo mkdir /mnt/cdrom --建立目录
sudo mount /dev/cdrom /mnt/cdrom --加载光盘内容到这个目录
tar -zxpf /mnt/cdrom/VMwareTools-*.tar.gz --解压到当前目录
cd vmware-tools-distrib --进入安装包目录
ls

Ubuntu 64-2016-04-18-15-58-27

可以看到 vmware tools 安装包目录下有 vmware-install.pl,有时还有 vmware-install.real.pl。但由于 vmware tools 需要 gcc 来重新编译,所以在安装 vmware tools 前需要先确保 gcc 已经安装:

1
2
sudo apt-get update
sudo apt-get install build-essential

然后再安装vmware tools

1
sudo ./vmware-install.real.pl

第一个问题问,现在有 open-vm-tools 了,是否还要用这个老的,默认是 no,选 yes。我试用了一下 open-vm-tools,缺陷依然太多,没法用。

然后一路回车到结束。安装完成时提示你,如果是图形界面的话,需要手动启动 /usr/bin/vmware-user,然后注销重登录。其实这步作用不大,系统关机下次再开效果是一样的。

Ubuntu 64-2016-04-18-16-37-35

如果之前没有安装 gcc,有时就会出现以下情况:

Ubuntu 64-2016-04-18-15-59-02gcc 路径为空,无限循环无法往下

这时需要用 Ctrl+C 中断,安装 gcc 后重新安装 vmware tools

3. 安装 Google Chrome 不用翻墙

因为下载浏览器的实际域是 dl.google.com,没有被墙。(北京联通)当然这个域名用浏览器是打不开的,因为它只提供下载,但知道路径的话就可以用 wget 直接下载了。

1
2
3
4
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb --64位
wget https://dl.google.com/linux/direct/google-chrome-stable_current_i386.deb --32位
sudo dpkg -i google-chrome*.deb
sudo apt-get install -f

4. 压缩虚拟磁盘,减少宿主机磁盘占用。

虚拟机用得时间长了,虚拟硬盘文件会变大。VMware 提供了清理磁盘功能释放空间,在 Windows 下很正常,但在 linux 下提供的 vmware-toolbox-cmd 有 bug,并不能起到压缩作用,需要先用零数据覆盖磁盘,再使用 vmware tool 的磁盘清理功能。

1
2
3
sudo dd if=/dev/zero of=zerowipe bs=1024x1024
sudo rm zerowipe
sudo vmware-toolbox-cmd disk shrinkonly

检查语言支持并补充安装,特别是使用 Minimal CD 在线安装的系统。
安装语言选择控制面板:

1
sudo apt-get install language-selector-gnome

检查缺失语言:

1
sudo apt install $(check-language-support)

5. 减少 /var/log 体积

1
sudo journalctl --vacuum-time=1d # 普通用户不看日志。要延长就改成 7d 或 30d

heidisql_logo

HeidiSQL 是个挺好用的 Windows 下轻量级 MySQL / Ms SQL / PostgreSQL 客户端。官网地址:http://www.heidisql.com/ 。功能不写了,反正都差不多。 它提供的 SSH Tunnel 连接方式这里记一笔备忘。

HeidiSQL 的 SSH Tunnel 连接方式其实就是先 SSH 连接到目标主机,再以目标主机的身份,连接到 MySQL 服务器。这有两种情况,一种是出于安全因素,数据库只允许本机或者有限几个 IP 访问,另一种是 MySQL 服务器和 SSH 目标主机在同一局域网内,而该局域网的多台机器只有 SSH 主机可以被外界直接连接。总之就是 MySQL 机无法被直接连接到,要通过 SSH 主机中转。

未命名

在中小网站中,数据库只允许 localhost / 127.0.0.1 连接是很常规的安全配置。但往往又存在需要后台操作数据的时候,于是有时会搭配 phpMyAdmin 这样的网页端方案,或者就是用 SSH Tunnel 这样的变通远程连接。

所以 HeidiSQL 的连接设置在选择网络类型为『MySQL (SSH Tunnel)』时也有所不同,除多出一个 『SSH隧道』选项页外,填写的参数也有变化。在 SSH Tunnel 模式下,设置页填写的是 SSH主机如何连接到 MySQL 服务器.

无标题SSH 主机(不是本机)如何连接到数据库,很多中小网站的数据库只允许本地访问,则这里应当填写 127.0.0.1

而 SSH 隧道页填写的则是 本机如何连接到 SSH 主机,由于 SSH Tunnel 依赖 Putty 软件包中的 Plink.exe 程序,所以需要指定 plink.exe 的位置,或者索性复制一个到 HeidiSQL 同目录下。同时,SSH 除用户名密码连接方式外,还有公私钥系统的连接方式,需要通过 Putty 软件包的 puttygen.exe 将私钥文件转成 Putty 专用的 .ppk 格式。

无标题本机如何连接到 SSH 主机

填写连接到 SSH 主机的用户名、密码,如果使用私钥文件的话,密码可以为空。

保存以后配置就完成了。并不需要去配置 Putty.exe 的任何内容。不知道为什么百度搜出来的好多博客都花了不少篇幅去写怎么配置 Putty,略扯淡。配置 Putty 与使用 HeidiSQL 并无直接关系。

一句话总结:

在普通模式下设置页填写的是运行 Heidi 的机器如何连接到目标 MySQL 服务器,而在 SSH Tunnel 模式下,需要先从运行 Heidi 的机器连接到 SSH 主机,再以 SSH 主机的身份连接到数据库服务器。

AWS 现在提供新注册帐号一年免费服务,1台1G内存30G硬盘的 VPS,每月 15G 流量,可以用来架 VPS 或者博客主机,虽然流量不大,但日韩机房的速度很不错。

  1. 首先需要在主机配置面板的安全设置中,把入口流量防火墙的 1723 端口打开。否则配置正确也会被防火墙挡上。

  2. 安装 PPTPD 服务。

    1
    2
    sudo apt-get update
    sudo apt-get install pptpd
  3. 编辑 pptpd.conf 配置文件。

    1
    sudo vim /etc/pptpd.conf
1
2
3
4
#其它的不用动,注意 option、localip、remoteip 三项即可
option /etc/ppp/pptpd-options
localip 192.168.9.1
remoteip 192.168.9.11-30
  1. 编辑 pptpd-options 配置文件。
    1
    sudo vim /etc/ppp/pptpd-options
1
2
3
4
5
6
7
8
9
#refuse,require 五项通常都是默认的
refuse-pap
refuse-chap
refuse-mschap
require-mschap-v2
require-mppe-128
#添加 Google DNS
ms-dns 8.8.8.8
ms-dns 8.8.4.4
  1. 编辑 chap-secrets 配置文件,添加 VPN 用户名和密码。
    1
    sudo vim /etc/ppp/chan-secrets
1
2
3
4
# Secrets for authentication using CHAP
# clientserver secret IP addresses
# 从前到后四项分别是用户名、PPTP/L2TP 选择、密码、允许连到该 VPN 的 IP 段,用空格或者 tab 分隔,用 * 表示通配
kaikai * 123456 *
  1. 打开 IP 转发
    1
    sudo vim /etc/sysctl.conf
1
net.ipv4.ip_forward = 1

需要重启服务:

1
sudo sysctl -p
  1. 添加 iptables 规则,这句根据服务商的不同会有不同,本句适用于 AWS
    1
    sudo siptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

并添加到启动项中,以便服务器意外重启后继续正常工作:

1
sudo vim /etc/rc.local
1
2
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
exit 0
  1. 配置完成,重启 pptpd 服务
    1
    sudo service pptpd restart

为方便位数较长的数字,我们经常会加上一些逗号或者故意在若干位数之间加大间隙以方便阅读。比如:

8326cffc1e178a82fd70f340f303738da877e886100万亿元津巴布韦币

这里的 100 万亿尽管没有直接加逗号『,』,但在每三位数字之间加大了间隙,所以如果学过英语,就可以比较方便地数出 Thousand, Million, Billion, Trillion 四级单位,所以这张钱是 One hundred trillion dollars。 但这是使用英语度量衡的国家,他们的语言以千为一级,所以以 3 位一级标比较方便,而中文是以 4 位一级的,这意味着在数数量级单位时,我们的思维变成了『千,百万,十亿,万亿』,于是得到『一百万亿』这个表达。

在汉语语系下,这个划分并不方便。直观的显然应该是 4 位一级,也就是『万,亿,万亿,兆,…… 』下去。比如:

三位一级:100,000,000,000,000

四位一级:100,0000,0000,0000

看起来显然是 4 位一级更直观。

接下来又到了是民族还是世界的争论,我认为在这种鸡毛蒜皮的『3 逗还是 4 逗』问题上,以下的见解是可以达成一致的:

  1. 这个标记本来就是辅助阅读用的小改进。除了它应当承担的用途外, 不承担别的用途。
  2. 无论 3 逗还是 4 逗,即使不熟悉的人,阅读起来也并不会造成障碍。本来这就是个辅助阅读的事情,辅助不了就当它不存在。
  3. 对于相应语言体系下的人,只有熟悉的分法才有较好的辅助作用。

所以我的结论是,如果作者和可以预知到的读者群体,都更熟悉 4 逗的话,那么相应的文字应该以 4 逗优先。反之 3 逗优先。假如某种文化是 5 位一个数量级的话,他们的出版物显然应该以 5 逗优先。如果作者与读者群的习惯不一致并且可以预期这种不一致的话,我觉得应该以读者优先。如果不可预知,那就是看作者个人习惯了。

于是,在我的博客和我可能的译作(英译中)的相关文字里,我都会尽量以 4 位为大数划分数量级。而若写的是英文博客的话,则会以 3 位划分。

说白了这就是个习惯问题,中文环境中并不需要强求以 3 位划分为准,4 位更习惯就用 4 位吧。

据我所知日语也是 4 位一级单位的,那么想必中国周边受中华文化影响的应该都是 4 位,大概。

0%