前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【参赛经验分享】分析js代码开启游玩新世界与Pierre Dellacherie算法本地验证

【参赛经验分享】分析js代码开启游玩新世界与Pierre Dellacherie算法本地验证

原创
作者头像
一颗小树
修改2021-08-13 18:06:40
2.7K2
修改2021-08-13 18:06:40
举报

腾讯极客挑战赛第四期:鹅罗斯方块

以下仅是我对于这个比赛的思考过程,可能是拿高分的技巧,但我并没有因此拿高分,本人算法水平有限大佬勿喷,对文章中的问题欢迎指出。

接触到赛题的一刻,我玩了几把,总结出以下几种规则

(1)方块顺序是固定的

(2)与其他俄罗斯方块相比多出悬停功能

(3)赛事标签为:算法题(主办方给出)

1.抓包分析部分

后面我想看看能不能钻分数上传的漏洞,对网页进行抓包

我们发现一串record和一个分数

我接着又玩了一把,并修改了分数进行上传

收到返回的结果是传入分数与计算不一致,我立马想到这个record可能是一个计算分数的依据

我尝试去获取record的含义是什么,结果真的好家伙,不找不知道,一找新世界的大门向我敞开

我使用浏览器F12的开发者工具搜索功能,搜索record这个单词,搜索到很多东西,我挨个查看,这个过程就不详细描述了,以下是我发现的几个比较好玩的东西

通过搜索,发现页面有很多注释,那么这些功能也可以进行利用,直接使用浏览器控制台即可

回放操作序列:game.pause();game.playRecord('N,D19,N,D17,N,D16,N,D14,N,D11,N,D9,N,D6,N,D4,N,D3,N,D1'.split(','));

提交上传成绩(会消耗提交次数):axios.post(api/upload, { record: 'N,D19,N,D17,N,D16,N,D14,N,D11,N,D9,N,D6,N,D4,N,D3,N,D1', score: 0 }).then(({ data }) => { console.log('提交结果', data); if(data.info) {console.log(data.info)} });

功能我不过多赘述,可以自行体验

到此我还没明白record到底是什么含义,所以我翻阅搜索到的文件(上图提示很明确,js文件代表的用途都写的很清楚,代码也不多翻一下也可以找到)

我找到一段控制台报错提示,我直接明白的record的含义

record:是一段用户的操作过程,包含移动旋转,生成新方块

我没看到悬停功能指向的代码,我根据这些提示,进一步尝试悬停在record中的体现方式,得出一个结论:下降没到底端直接New一个新方块则视为悬停,例如:D2,N,则视为下降2格悬停,并生成新方块

理解了上传数据的含义,那就不用手动玩这个俄罗斯方块了,并且可以利用record序列进行俄罗斯方块的排布,而且通过回放序列功能,也可以在网页上查看自己写的序列有没有问题(完全无视了速度增加导致手速反应不过来的问题)

大部分的游戏规则可以从tetris.game.min.js中获取(以下规则从此js文件中获取)

/*

•@Author: geek

•@LastEditors: geek

•@Description: 【俄罗斯方块游戏主文件】依赖 tetris.core

•@Src: https://geek.qq.com/tetris/js/tetris.game.js (编译前的源文件) *

•游戏介绍:

•1、将 10000 块按固定顺序出现的方块堆叠,有消除行即得分,看谁得分高

•2、游戏分正式模式和回放模式,正式模式用于 PK 打榜,回放模式(playRecord)目前仅提供用于 debug 操作记录和对应的分数(暂未开放使用)

•3、方块下落速度会随着出现的方块数量加快,每 100 个方块后,速度递减 100ms,原始速度 1000ms,最快 100ms

•4、画布垂直方向满屏后,结束游戏

•5、方块出现的总数最大为 10000 个,超过后结束游戏

•6、每个方块的类型(已有:I,L,J,T,O,S,Z 型方块)、形态(各类型每旋转90度后的形态)会从配置中按照统一顺序、限定概率地读取出来,保证所有人遇到的方块顺序和方块概率都一致

•7、积分规则:当前方块的消除得分 = 画布中已有的格子数 * 当前方块落定后所消除行数的系数,每消除 1、2、3、4 行的得分系数依次为:1、3、6、10(例:画布当前一共有 n 个格子,当前消除行数为2,则得分为:n * 3)

•8、游戏结束触发规则:1)、方块落定后触顶;2)、新建方块无法放置(画布上用于放置方块的格子中有已被占用的)

*

•注:游戏中优先判定是否结束游戏再计分。如:极限情况下,当前方块落定后产生了可消除行,但触顶或者超过最大方块数了,此轮不计分,直接结束游戏

•注:游戏使用的坐标系为 canvas 坐标系(坐标原点在左上角)详见:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes

* */

我认为重要的规则经过加粗显示,规则6说过,方块的顺序是固定的,因为每次游戏开始总是出现相同的,所以我进一步搜索方块的生成规则,以便透析10000个方块的具体类型与旋转角度。

规则6给出了方块顺序与角度顺序,所以我调整了本地pygame代码,以便调整做本地分数校验。

ps:之前也想出一个一个记录,但实在是太麻烦了

1.1 提前算出10000个方块类型和旋转角度

过程就不详细描述,我的方法是在新生成方块的地方进行断点,然后进行逐步分析结论如下:

从tetris.core.min.js中获取到一些重要的生成信息(200行附近代码)

var defaultBrickCenterPos = [4, 0]; //作为定值,不进行计算

var randomConfig = { a: 27073, M: 32749, C: 17713, v: 12358 };

key: "getBrickInfo", value: function getBrickInfo(randomNum, brickCount, brickCenterPos, mute) { var brickRawInfo = this.getRawBrick(randomNum, brickCount, mute); var _this$getBrickPos = this.getBrickPos(brickRawInfo, brickCenterPos, true) , isValid = _this$getBrickPos.isValid , brickInfo = _this$getBrickPos.brickInfo; return { isValid: isValid, brickRawInfo: brickRawInfo, brickInfo: brickInfo } }

key: "getShapeInfo", value: function getShapeInfo(randomNum, brickCount) { var shapes = this.shapes , colors = this.colors; var weightIndex = randomNum % 29; var stateIndex = brickCount % shapes[0].length; var colorIndex = brickCount % colors.length; var shapeIndex = 0; if (weightIndex >= 0 && weightIndex <= 1) { shapeIndex = 0 } else if (weightIndex > 1 && weightIndex <= 4) { shapeIndex = 1 } else if (weightIndex > 4 && weightIndex <= 7) { shapeIndex = 2 } else if (weightIndex > 7 && weightIndex <= 11) { shapeIndex = 3 } else if (weightIndex > 11 && weightIndex <= 16) { shapeIndex = 4 } else if (weightIndex > 16 && weightIndex <= 22) { shapeIndex = 5 } else if (weightIndex > 22) { shapeIndex = 6 } return { shapeIndex: shapeIndex, stateIndex: stateIndex, colorIndex: colorIndex }

key: "getRandomNum", value: function getRandomNum(v) { var a = randomConfig.a , C = randomConfig.C , M = randomConfig.M; return (v * a + C) % M }

以下是我进行优化后的JS代码,以便进行10000个方块的运算

//随机数种子

var randomConfig = { a: 27073, M: 32749, C: 17713, v: 12358 };

//计算下一个随机数v

function getRandomNum(v) { var a = randomConfig.a, C = randomConfig.C, M = randomConfig.M; return (v * a + C) % M };

//根据随机数、方块生成数进行新方块的信息生成

function getShapeInfo(randomNum, brickCount) { var shapes = new Array(); var weightIndex = randomNum % 29;

var stateIndex = brickCount % 4;//需要通过计算出来的方块,和他的形态数量进行取余,JS文件中不做此项操作

var shapeIndex = 0; if (weightIndex >= 0 && weightIndex <= 1) { shapeIndex = 0 } else if (weightIndex > 1 && weightIndex <= 4) { shapeIndex = 1 } else if (weightIndex > 4 && weightIndex <= 7) { shapeIndex = 2 } else if (weightIndex > 7 && weightIndex <= 11) { shapeIndex = 3 } else if (weightIndex > 11 && weightIndex <= 16) { shapeIndex = 4 } else if (weightIndex > 16 && weightIndex <= 22) { shapeIndex = 5 } else if (weightIndex > 22) { shapeIndex = 6 } return [shapeIndex , stateIndex] };

//运算过程:getRandomNum(12358)获取第一个随机数,getShapeInfo(随机数,生成的方块数)获得第一个方块信息,为6,0

//排列顺序:(已有:I,L,J,T,O,S,Z 型方块)、形态(各类型每旋转90度后的形态)规则6给出

//所以第一个是Z(数组是从0开始的,所以6是第7个Z),形态是0,所以是没有经过旋转,以此类推1为90度,2为180度(只有两种变换形态通过取余方式变成0或者1)

2.算法实现(Pierre Dellacherie算法)

查询资料,我发现最具代表性的AI算法为Pierre Dellacherie算法,所以我对github中分享的pygame游戏其中有Pierre Dellacherie算法,进行修改,用来对活动中的俄罗斯方块进行模拟。发现效果并不理想,可能没有考虑进悬停因素造成的。

我想有了10000个方块和形态,然后又有过程代码可以省去人工操作(广大极客网友可以自行建立算法,Pierre Dellacherie算法我已经踩坑了,算法参数调整并不能带来高分),或许可以慢慢将10000个方块手动搭起来获得高分?

仿制游戏:https://github.com/xiaochen-1/TXTetris(内置AI算法,pygame游戏)

不会输出序列答案,仅提供分数参考(校验分数仅代表本地游戏成绩,与线上计分方式有所不同)

游戏作者为:Charming2015(Github名称)

本人仅修改:游戏高度、宽度、方块生成顺序、生成形态、算法权重

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云开发 CLI 工具
云开发 CLI 工具(Cloudbase CLI Devtools,CCLID)是云开发官方指定的 CLI 工具,可以帮助开发者快速构建 Serverless 应用。CLI 工具提供能力包括文件储存的管理、云函数的部署、模板项目的创建、HTTP Service、静态网站托管等,您可以专注于编码,无需在平台中切换各类配置。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档