今天打开微博,发现还有人在做这个游戏,希望TX的童鞋不要介意把一份不标准答案公布出来(如果有必要,我可以删除,呐,校招求职的童鞋,请不要盲目抄写,因为我的答案或许是最差的解题思路哟)。 那么我就先用常规解题思路走一遍,然后按照取巧偷懒的方式跑一遍,开打!开打!

答题网址

http://codestar.alloyteam.com/

第一关

题目描述: 一个中规中矩的登录框,先不看代码,首先什么都不填写,点击提交试试看,诶,提示我们“姓名和邮箱是你的闯关凭证,不能为空”,哦,那么随便填写一个吧,出于对作者的尊重,咱们写个真的吧。^ ^ 填写之后,在点击提交,会提示“打不开?抓一下包看看……”,腾讯组的童鞋只是在过滤不用chrome inspector/fiddle的童鞋嘛。 F12后,继续点击按钮。看到按钮事件会向http://codestar.alloyteam.com/signup地址提交一个POST请求, 而一般的返回结果为:

{"retcode":10001,"result":"请设置隐藏域的值"}

提示有点太明确了吧,那么看看页面结构吧,发现表单果然有一个字段timestamp隐藏域value木有赋值。

<div id="board">
	<a id="close" href="#">&times;</a>
	<div class="info">输入个人信息,打开后门门锁</div>
	<h2>后 门</h2>
	<form class="mt30" id="msgForm">
		<input type="text" name="name" placeholder="姓名" />
		<input type="text" name="email" placeholder="邮箱(后面用到)" />
		<input type="hidden" name="timestamp" />
		<div class="mt30"><button class="btn" id="btnSubmit">打开</button></div>
	</form>
</div>

翻看页面内的内联脚本,common.js,没有看到对时间格式的检查相关的函数,那么就默认需求的时间格式是纯数字的毫秒数好了(错了再换格式呗。)

解题答案

document.getElementsByName('name')[0].setAttribute('value','soulteary'),
document.getElementsByName('email')[0].setAttribute('value','soulteary@qq.com'),
document.getElementsByName('timestamp')[0].setAttribute('value',(new Date-0)),
document.getElementById('btnSubmit').click();

第二关

题目描述: 点开题目,明显的是之前让大家眼前一亮的CSS画图,看了一下图形,考察点是椭圆(圆角),图形旋转以及三角形实现。 查看页面源码,发现腾讯童鞋很好心的放了一个参考链接,那么就简单的看一下吧。 http://www.alloyteam.com/2012/10/css3-draw-qq-logo/ 根据图上左边的椭圆给的数据,宽40,高60,那么得到补充规则

#model_1{
    border-top-left-radius: 20px 30px;
    border-top-right-radius: 20px 30px;
    border-bottom-right-radius: 20px 30px;
    border-bottom-left-radius: 20px 30px;
}
/* 或者 下面的规则*/
#model_1{
    border-radius:50% 50%;
}

而右边的图形,根据60deg等提示得到下面的规则,这部分题不严谨,其实可以单纯的设置border-bottom-color和border-top-color来实现三角形,但是有一个不错的细节点是按照右边的那个点为原点旋转。

#model_2  {
    -webkit-transform: rotate(-60deg);
    border-bottom:10px solid transparent;
    border-top: 20px solid transparent;
    -webkit-transform-origin: top right;
}

解题如下:

document.getElementById('textarea_1').innerHTML = 'border-radius:50% 50%;',
document.getElementById('textarea_2').innerHTML = '-webkit-transform: rotate(-60deg);border-bottom:10px solid transparent;border-top: 20px solid transparent;-webkit-transform-origin: top right;',
document.getElementById("container").onkeyup({target:document.getElementById('textarea_1'),id:'textarea_1'}),
document.getElementById("container").onkeyup({target:document.getElementById('textarea_2'),id:'textarea_2'});

第三关

题目描述: 应该是考察快速使用api的的能力以及如何快速完成任务吧。 到坦克战场后,你发现首先你是没有坦克的,那么创建一个好了,编辑你的坦克,发现你的坦克的各种事件都是空的(当然是空的) 翻开文档,发现支持的参数还蛮多的,包括按下按键以及发送消息等,但是真心不想写WASD或者上下左右的事件了…好在腾讯的童鞋也没有较真非要写,而且他们的demo坦克比较菜… 那么我们搞一个固定炮台过关好了,将demo中的开火以及旋转车头复制进你的坦克事件中,坐等过关吧。

/**
*robot主循环
**/	
run:function(){
		this.loop(function(){
		this.say("小样,能打到我么?","deepskyblue");
		this.setTurn(60,function(){
			this.setTurn(-120,function(){
				this.setTurn(240,function(){
					this.setTurn(-120);
					this.execute();
				});
				this.execute();
			});
			this.execute();
		});
		this.execute();	
	})
},

/**
*看到其他robot的处理程序
**/
onScannedRobot:function(e){
	this.fire(1);
}

第四关

题目描述: 这个大概是对常见问题的掌握和理解吧,多数问题都是大家讨论烂掉吧,不过或许出题人想看一些非主流答案,或者对效率苛刻一下? //完整copy对象,用个简单的实现吧(这道题的出题者想要的是深拷贝的写法^ ^)

JSON.parse(JSON.stringify(arr));

//写一个常规的,这个效率不是最优的,一会再写一篇这个效率的blog吧

s.replace(/(^\s*)|(\s*$)/g, "");

//写一个常规的,这个效率不是最优的,一会再写一篇这个效率的blog吧

s.replace(/(^\s*)|(\s*$)/g, "");

//最后一个利用slice特性将类数组转换

Array.prototype.slice.call(list,0);

第五关

题目描述: 考察分析能力以及简单的数学能力(动态规划),这个页面中的js我加了注释先写在下面吧。 里面有好几处细节写的超级棒!(yin dang,who’s author?)

(function() {
	//创建ajax请求,判断是否过关
    function resultChecker() {
        ajax("/pass", {method: "POST",data: '{"q":1,"s":5,"_t":' + token(v) + "}",contentType: "application/json",onSuccess: function() {
                hideBoard();
                document.getElementById("btnNext").className += " show";
                alert("\u8fc7\u5173\uff01\u4e0b\u4e00\u5173\u7684\u5165\u53e3\u5df2\u6253\u5f00")
            }})
    }

    //提示剩余次数
    function showOverage() {
    	//提示剩余次数,如果没次数了,就刷新页面
        n.innerHTML = f;
        n.parentNode.title = "\u8fd8\u6709 " + f + " \u6b21\u5c1d\u8bd5\u7684\u673a\u4f1a";
        k.className = "tada";
        0 >= f && (alert("\u8b66\u94c3\u5927\u54cd\uff0c\u95ef\u5173\u5931\u8d25\uff01"), location.reload())
    }


    //计分函数
    function theScore() {
        var openBox = l.getElementsByClassName("open");
		//重置分数
        e = 0;
        for (var a = openBox.length; a--; )
            e += Number(openBox[a].innerHTML);
        //显示计分更新
        w.innerHTML = e
    }

    //每个元素的点击事件
    function clickBox(elem) {
    	//失败提示重置(隐藏)
        failBox.style.visibility = "hidden";
        //获取点击元素的row和id,当前行的开始和结束
        //把同一行的元素的选中状态重置掉
        for (var row = Number(elem.parentNode.id.slice(4)), id = Number(elem.id.slice(7)),
        	start = row * (row - 1) / 2 + 1, end = (row + 1) * (row + 1 - 1) / 2 + 1; 
    		start < end; start++)
        {
            document.getElementById("folder_" + start).classList.remove("open");
        }
        //给当前元素添加选中状态
        elem.classList.add("open");
        //如果不是最后一行的元素
        if (12 !== row) {
        	//重置选中元素下面所有的节点的状态
            for (start = (row + 1) * (row + 1 - 1) / 2 + 1; 78 >= start; start++)
                document.getElementById("folder_" + start).className = "folder";
            //设置选中元素的子节点可以选择
            document.getElementById("folder_" + (row + id)).classList.add("able");
            document.getElementById("folder_" + (row + id + 1)).classList.add("able");
            //计算当然的数值结果
            theScore()
        } else{
        	//已经到了最后一行了,那么触发对查找结果求和的事件,计算结果并和答案对比,如果一致,那么调用AJAX检查是否过关
        	//反之,显示查找失败,寻找次数减一,更新剩余查找次数提示
            theScore(), s === e ? resultChecker() : (failBox.style.visibility = "visible", f--, showOverage())
        }
    }

    //计算过关的token
    function token(key) {
        for (var mask = 5381, index = 0, count = key.length; index < count; ++index){
        	//把key按位转换为ASCII数值
        	//mask移位并和key的每一位求和
            mask += (mask << 5) + key.charAt(index).charCodeAt();
        }
        //获得最后的过关token
        return mask & 2147483647
    }
    var v = log.innerHTML,	//过关的KEY
    g = [],//全局的随机数字数组
    l = document.getElementById("box"), //模版容器
    w = document.getElementById("currValue"), 
    failBox = document.getElementById("failBox"), //失败提示
    s = 0, //这次随机数字的最大和
    e = 0, //当前选择的元素获得的分数和
    f = 2, //剩余次数计数器
    k = document.getElementById("bell"), //尝试机会的整个元素
    n = document.getElementById("times"), //提示剩余次数提示的行内元素
    h = pfx("animation"); //酱油变量, 先储存前缀CSS属性
    //先判断是神马浏览器,然后根据对象中设置的浏览器类型绑定动画结束事件,把bell的class清空
    on(k, {moz: "animationend",webkit: "webkitAnimationEnd",
        ms: "MSAnimationEnd",o: "oAnimationEnd"}[h.toLowerCase().replace("animation", "")], function() {
        k.className = ""
    });
    //初始化记分
    showOverage();

    resultChecker()

    //委托每个元素的点击事件到box上
    l.onclick = function(elem) {
        elem = elem.target;
        //当这个元素可以被点击的情况下,触发这货的点击事件
        elem.classList.contains("able") && clickBox(elem)
    };

    //创建DOM元素
    (function() {
    	//用78个随机数填满全局数组g
        for (var index = 1; 78 >= index; index++){
            g[index] = Math.ceil(100 * Math.random()) + 100;
        }
        //把78个元素打散在12行中
        for (var tpl = "", id = 0, index = 1; 12 >= index; index++) {
        	//每行放每行的序号个元素
            for (var tpl = tpl + ('<div id="row_' + index + '">'), count = 1; count <= index; count++){
            	//把数组g中的内容填出来
                id++, tpl += '<span class="folder" id="folder_' + id + '">' + g[id] + "</span>";
            }
            tpl += "</div>"
        }
        //填充box内容
        l.innerHTML = tpl
    })();

    //预先计算最大值
    (function() {
    	/** 
    	*  firstOne 每行开始的第一个元素
    	*  index 某行中的某列元素的全局索引
    	*  data 储存每个节点的最大叠加数值的容器
    	*  row 当前行
    	*  offset 当前元素的列增量
    	**/
        for (var firstOne, index, data = [], row = 12; 0 < row; row--) {
            firstOne = row * (row - 1) / 2 + 1;
            //将所有有节点的父节点的数值和子节点求和并保存在当前节点
            for (var offset = 0; offset < row; offset++){
                index = firstOne + offset, data[index] = 12 === row ? g[index] : g[index] + (data[index + row] > data[index + row + 1] ? data[index + row] : data[index + row + 1])
            }
        }
        //把结果保存到全局变量中
        s = data[1]
    })();

    //选择第一个元素,标记其可以点击,并触发它的点击事件
    h = document.getElementById("folder_1");
    h.classList.add("able");
    clickBox(h);
    //干掉DOM中的KEY
    clear();

    //bla bla bla...
    console.log("\n\u7b2c\u4e94\u7ae0\uff1a\u8d44\u6599\n");
    console.log("\u201c\u8fd9\u4e2b\u7684\u662f\u8c01\u5efa\u7684\u6587\u4ef6\u5939\uff1f");
    console.log("\u770b\u6765\u8981\u6309\u4e00\u5b9a\u987a\u5e8f\u6253\u5f00\uff0c\u4f7f\u5f97\u4fdd\u5bc6\u7b49\u7ea7\u6700\u9ad8\uff0c\u624d\u80fd\u627e\u5230\u6211\u8981\u7684\u8d44\u6599\u3002\u201d\n")
})();

看完代码,估计你也就知道我文章开头说的如何直接跳过做题一路next了吧… 但是我们还是继续把这个题做完吧,因为蛮好玩的! 实现工具如下:

//腾讯前端特工第五题,答题工具
var g=[],//页面中的数字元素
	result = 0,
	path = [];
//将页面中的元素还原为一个一维数组
var box = document.getElementById('box').childNodes;
for(var i=0, j=box.length; i<j; i++){
	var row = box[i].childNodes;
	var index = 0;//储存当前元素索引
	for(var m=0, n=row.length; m<n; m++){
		var start = (i+1)*(i+1-1)/2+1;
		var index = start+m;
		g[index] = parseInt(row[m].innerHTML);
	}
}
//先把结果计算出来吧, 实现工具会更简单点
for(var firstOne, index, data = [], row = 12; 0 < row; row--) {
    firstOne = row * (row - 1) / 2 + 1;
    //将所有有节点的父节点的数值和子节点求和并保存在当前节点
    for (var offset = 0; offset < row; offset++){
    	index = firstOne + offset;

    	if(row===12){
    		path[index] = [index];
    		data[index] = g[index];
    	}else{
    		var leftNode = data[index + row];
    		var rightNode = data[index + row +1];
    		if(leftNode>rightNode){
    			path[index] = [index, index+row];
    		}else{
    			path[index] = [index, index+row+1];
    		}
    		data[index] = g[index] + (leftNode > rightNode ? leftNode : rightNode);
    	}
    }
}
result=data[1];
document.getElementById('currValue').innerHTML = result;
var start = path[1];
for(var i=0, row=12, step=1; i<row; i++){
	step = path[step];
	if(step[1]){step = step[1];}
	document.getElementById("folder_" + step).setAttribute("class", "folder able open");
}
alert('我只能帮你到这里了,还愣着干嘛,点一下最后的那个数字,然后坐等邮件吧,如果你有微博的话,欢迎粉我 @soulteary');

但是腾讯前端小组的童鞋很机智的在common.js中声明了一个clear的函数,在页面中内联的脚本中把这货干掉了,并且这些都是在闭包中做的… 换言之,少年你直接写脚本搞不定滴水,当然如果你用fiddle之类的代理替换页面的也可以哟! 那么我们来用一种调试的思路快速搞定这些题吧!

先打开chrome调试工具,然后选择source,在当前HTML文档页面中找到压缩成一坨的代码,随便下个断, 然后在下面的Watch Expressions中添加一个变量“log”,然后刷新页面。

不出意外,浏览器中断了吧,查看我们监视的变量“log”的任何一个innerHTML/innerText/OuterHTML/OuterText,发现诸如这样的MD5字符串:“3b3d8a5f33d811ca00cf89bcdf03e6e1”,然后不要客气,复制出来吧。 (注意监视变量名称可以搜索源码中的log.innerHTML前的名称定义)

然后将页面源码中的诸如这样的代码复制出来。 以第一关为例,每一关由于压缩的原因,变量名不一致,但是方法是一致滴!

//把刚刚的key赋值一下,变量名为下面的ajax函数的data字段的实参名
var f = "3b3d8a5f33d811ca00cf89bcdf03e6e1"

//找到return 一个变量 & 一个字符串的函数
var e = function(c) {
    for (var a = 5381, b = 0, d = c.length; b < d; ++b)
        a += (a << 5) + c.charAt(b).charCodeAt();
    return a & 2147483647
}
//找到/pass的ajax复制出来
ajax("/pass", {method: "POST",data: '{"q":1,"s":1,"_t":' + e(f) + "}",contentType: "application/json",onSuccess: function(c) {
    hideBoard();
    document.getElementById("btnNext").className += " show";
    alert("\u8fc7\u5173\uff01\u4e0b\u4e00\u5173\u7684\u5165\u53e3\u5df2\u6253\u5f00")
}})

然后扔到console,回车一下,等待弹出下一关已经打开的提示窗,然后按一下页面中的下一关,继续闯关!

本文写于抑郁的11月中旬,如果冒犯或者损害到原作者的利益或者神马,请邮件告知,第一时间销毁处理。