使用前端方式挑战 Chrome 小恐龙游戏高分

本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2018年09月13日 统计字数: 2506字 阅读时间: 6分钟阅读 本文链接: https://soulteary.com/2018/09/13/use-the-front-end-approach-to-challenge-chrome-dinosaur-high-scores.html


使用前端方式挑战 Chrome 小恐龙游戏高分

今天看论坛发现有人发帖说 Chrome周年庆祝 在“小恐龙”游戏中埋入了新的菜单,可以触碰的 蛋糕 以及触碰之后获得的 生日帽

看到现在“小恐龙”这个彩蛋的改进,往事历历在目。但是很快,帖子中有人晒高分引起了我的注意:这个游戏虽说简单,但是在分数达到一定程度后,加速度会让玩家很难继续游戏。

记得很早之前在机场玩过这个游戏,记忆中获取三千以上的分数还是挺困难的,更别提八千分,所以我这里推测这个玩家应该是有一些“特别的技巧”。

在游戏中取得令人叹为观止的高分,一般来说无非三种手段:

  1. 练习:勤学苦练,熟能生巧,加上极致的耐心。
  2. 工具:使用一些辅助设施来模拟人的操作。
  3. 外挂:针对难度或者得分倍数进行改造或者直接对结果进行修改、甚至破坏游戏规则。

第一种方式我们暂且略过,后面如果实在闲,再进行尝试,我们直接开始后面两种方案的尝试。

编写辅助工具

奔跑的小恐龙

君子性非异也,擅假于物也。

网上有个老外曾经编写过一个根据判断X轴方向小恐龙和前面物品距离进行自动跳跃的程序,在稍加改造后,将下面的代码贴到控制台中,即可完成小恐龙自动奔跑的神操作。

这里添加了代码避免小恐龙跳过蛋糕、避而不吃,以及调整了对障碍物需要跳跃的高度判断,防止小恐龙看到低飞的鸟儿迎面而上,当然,还有能够在代码执行后,自动开始游戏。

 
  1. function TrexRunnerBot() {
  2.  
  3. const makeKeyArgs = (keyCode) => {
  4. const preventDefault = () => void 0;
  5. return {keyCode, preventDefault};
  6. };
  7.  
  8. const upKeyArgs = makeKeyArgs(38);
  9. const downKeyArgs = makeKeyArgs(40);
  10. const startArgs = makeKeyArgs(32);
  11.  
  12. if (!Runner().playing) {
  13. Runner().onKeyDown(startArgs);
  14. setTimeout(() => {
  15. Runner().onKeyUp(startArgs);
  16. }, 500);
  17. }
  18.  
  19. function conquerTheGame() {
  20. if (!Runner || !Runner().horizon.obstacles[0]) return;
  21.  
  22. const obstacle = Runner().horizon.obstacles[0];
  23.  
  24. if (obstacle.typeConfig && obstacle.typeConfig.type === 'SNACK') return;
  25.  
  26. if (needsToTackle(obstacle) && closeEnoughToTackle(obstacle)) tackle(obstacle);
  27. }
  28.  
  29. function needsToTackle(obstacle) {
  30. return obstacle.yPos !== 50;
  31. }
  32.  
  33. function closeEnoughToTackle(obstacle) {
  34. return obstacle.xPos <= Runner().currentSpeed * 18;
  35. }
  36.  
  37. function tackle(obstacle) {
  38. if (isDuckable(obstacle)) {
  39. duck();
  40. } else {
  41. jumpOver(obstacle);
  42. }
  43. }
  44.  
  45. function isDuckable(obstacle) {
  46. return obstacle.yPos === 50;
  47. }
  48.  
  49. function duck() {
  50. Runner().onKeyDown(downKeyArgs);
  51.  
  52. setTimeout(() => {
  53. Runner().onKeyUp(downKeyArgs);
  54. }, 500);
  55. }
  56.  
  57. function jumpOver(obstacle) {
  58. if (isNextObstacleCloseTo(obstacle))
  59. jumpFast();
  60. else
  61. Runner().onKeyDown(upKeyArgs);
  62. }
  63.  
  64. function isNextObstacleCloseTo(currentObstacle) {
  65. const nextObstacle = Runner().horizon.obstacles[1];
  66.  
  67. return nextObstacle && nextObstacle.xPos - currentObstacle.xPos <= Runner().currentSpeed * 42;
  68. }
  69.  
  70. function jumpFast() {
  71. Runner().onKeyDown(upKeyArgs);
  72. Runner().onKeyUp(upKeyArgs);
  73. }
  74.  
  75. return {conquerTheGame: conquerTheGame};
  76. }
  77.  
  78. let bot = TrexRunnerBot();
  79. let botInterval = setInterval(bot.conquerTheGame, 2);

当然,为了你能够在移动端的复制便捷,我这里提供了压缩版的代码。

 
  1. function TrexRunnerBot(){function f(){Runner().onKeyDown(d);setTimeout(function(){Runner().onKeyUp(d)},500)}var b=function(a){return{keyCode:a,preventDefault:function(){}}},c=b(38),d=b(40),e=b(32);Runner().playing||(Runner().onKeyDown(e),setTimeout(function(){Runner().onKeyUp(e)},500));return{conquerTheGame:function(){if(Runner&&Runner().horizon.obstacles[0]){var a=Runner().horizon.obstacles[0];if((!a.typeConfig||"SNACK"!==a.typeConfig.type)&&50!==a.yPos&&a.xPos<=18*Runner().currentSpeed)if(50=== a.yPos)f();else{var b=Runner().horizon.obstacles[1];if(b&&b.xPos-a.xPos<=42*Runner().currentSpeed)Runner().onKeyDown(c),Runner().onKeyUp(c);else Runner().onKeyDown(c)}}}}}var bot=TrexRunnerBot(),botInterval=setInterval(bot.conquerTheGame,2);

如果你想在桌面端浏览器游玩小恐龙游戏,除了断网的操作外,还可以直接访问 chrome://dino/ 这个地址。

如果你想在手机上体验小恐龙自动奔跑,请参考下面的操作:

  1. 飞行模式后打开 Chrome ,然后访问任意页面,呼出小恐龙界面。
  2. 将上面的代码粘贴到地址栏。
  3. 在地址栏前手动加上 javascript: ,然后确认。

你的小恐龙就自个去浪了,不过这里你获取分数的速度依然和正常游玩的玩家是一样的,小恐龙走一步记一分。

那么,接下来我们换个套路。

改写计分逻辑

与其说改写,不如说“劫持”。ES5 有一个很古老的 API, Object.defineProperty(),借助这个 API ,我们能够轻易的修改现有对象上的属性,配合重新定义对象具体内容的 getter、 setter 描述符,可以做到对于属性的劫持操作,是不是很眼熟?没错,这个方案也是老生常谈的 MVVM 框架的双向数据绑定的实现方案之一。

 
  1. let hackScore = 0;
  2.  
  3. Object.defineProperty(Runner.instance_, 'distanceRan', {
  4. get: () => hackScore,
  5. set: (value) => hackScore = value + Math.floor(Math.random() * 1000),
  6. configurable: true,
  7. enumerable: true,
  8. });

同样提供了压缩版本。

 
  1. var hackScore=0;Object.defineProperty(Runner.instance_,"distanceRan",{get:function(){return hackScore},set:function(a){return hackScore=a+Math.floor(1E3*Math.random())},configurable:!0,enumerable:!0});

将上面代码执行之后,再次运行程序,你会发现你获取分数的速度提升了一千倍。

如果你将第一个方案和这个方案的代码结合,会获得一个能够自动奔跑获得高分的“智能小恐龙”。

不过因为我们的“外挂”是基于计时器进行距离计算并模拟用户操作的,当你获得很高很高的分数之后,障碍物推进速度过快,一旦你进行窗口的来回切换,游戏进行暂停和游玩的状态切换,很大概率上“外挂”操作会延时,导致 GAME OVER

如何能避免这个事情呢,我们来讲讲第三个套路。

破坏规则大法

如果说第一个方案起码还有付出时间成本,模拟玩家操作一步一步按部就班的获取分数;第二个方案偷天换日,一步当一千步使;那么接下来的方案就显得十分无耻了。

 
  1. Runner.instance_.gameOver=function(){};

下面代码在执行之后,会清空游戏的中断逻辑,配合第二个方案的快速获得分数的代码,你可以得到一只拥有穿越障碍物能力的小恐龙:勇往无前,永不停歇,分数不停的增长,直到报错。

其他

相关代码我推到了 GitHub 上,有兴趣的同学也可以访问,以了解更多内容。

先写到这里,去看苹果发布会了。

--EOF

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值