共计 7575 个字符,预计需要花费 19 分钟才能阅读完成。
有些奇淫技巧玩好的话,就能提升自己的逼格,这不,一行 js
代码实现一个贪吃蛇小游戏就成了装逼到了最高境界嘛!代码如下:
(function(){var s = [41,40],d = 1,f = 43,x,c = document.createElement('canvas');c.width=400;c.height=400;c.style.background="#535353";c.textContent="当前浏览器不支持 canvas 标签";b=c.getContext('2d');function w(s,c){b.fillStyle = c;b.fillRect(s % 20 * 20, ~~(s / 20) * 20 , 18 , 18);};document.onkeydown=function(e){d = s[1] - s[0] == (x = [-1,-20,1,20][(e || event).keyCode - 37] || d ) ? d : x;};!(function(){s.unshift(x = s[0] + d);if(s.indexOf(x,1) > 0 || x < 0 || x > 399 || d == 1 && x % 20 == 0 || d == -1 && x % 20 == 19)return alert('游戏结束!');w(x,'#2396ef');x === f ? (()=>{while (s.indexOf(f = ~~(Math.random() * 399)) > 0);w(f,'#35e3dc');})() : w(s.pop(),'#535353');setTimeout(arguments.callee,300);})();document.body.appendChild(c);})();
ps
: 我不是来装逼的。!
好了,让我们来运行一下这行代码,看一下效果:
看动图看着不过瘾?,好,你自己去线上看看 demo
可以撒,具体示例。
装逼完成,
。
好了,言归正传,我怎么可能是来装逼的,我要来分析一下,这个是怎么玩的,这才是我的目的。
让我们拆分代码来看:
首先,最外层包裹了一个自调用函数,如下:
(function(){// 具体内容})();
// 自调用函数当然不止这样的写法
然后第二步,要画一个场景,那很明显,要用 HTML5
的canvas
标签,我们可以采用 document.createElement()
这个方法来创建一个元素,继续:
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
})();
蛇运动的场景肯定是固定大小的,也就是说,我们要给 canvas
设置宽和高,在这里,我就是设置的400X400
, 然后给场景加一个背景。就这样:
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
})();
创建了 canvas
元素,我们要添加到 DOM
网页中去,所以用 appendChild()
来添加。如下:
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
document.body.appendChild(c);
})();
哦,有些浏览器可能不支持 canvas
标签,所以我们不能忘了给一个优雅的提示:
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
c.textContent="当前浏览器不支持 canvas 标签";
document.body.appendChild(c);
})();
场景画好了,接下来,我们要在场景上画蛇,所以 canvas.getContext('2d')
这个方法是必不可少的。
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
document.body.appendChild(c);
})();
接下来分析蛇的构成,实际上这里的场景,我们可以看成是 400
个20*20
的块组成,那么蛇也就可以看成一个块一个块的组成, 用技术术语来说就是一个队列, 也就是一个数组,[20]
。在这里初始化蛇为 2
个块,也就是[20,20]
,那么蛇初始化位置还是稍微调整一下嘛,所以也就改成[40,40]
, 其实在这里第一次出现的时候,是隐藏了一个食物的,默认就在蛇初始化位置的下一格,然后蛇会立即吃掉,然后就随机出现下一个食物的位置了,因此默认食物的位置是40
。为了一个方便细小的微差,就稍微调大一点, 为43
。好了,现在我们要知道蛇运行的方向,蛇可以上下左右活动,那么,我们定义为s[1] - s[0]
, 这是根据位置来计算的。咱们先定义蛇再说:
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的
d = 1,// 定义蛇活动方向,默认向右
f = 42;// 默认食物的位置
document.body.appendChild(c);
})();
接下来,开始绘制蛇,蛇活动的轨迹与食物。定义一个函数,利用 canvas.fillStyle()
填充背景,在这里蛇与食物还有蛇活动的轨迹都是一个小方块矩形,这样我们就使用 canvas.fillRect()
来绘制一个矩形,这个方法有四个参数,如下图所示:
蛇活动的轨迹坐标,也就是定义的蛇数组的坐标,因此第一个参数就是 s % 20 * 20
, 第二个参数就是~~(s / 20 * 20)
。关于~~
这个操作符,如果不理解的话,可以看我的文章浅谈 JavaScript 位操作符。这里感觉有些抽象,实际上,需要靠自己的想象力来想象理解。
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的,代表默认向右方向
d = 1,// 定义蛇活动方向,默认向右, 蛇运动方向为 s[1] - s[0]
f = 42;// 默认食物的位置
// 这个函数既是绘制蛇方块,也是绘制食物与蛇活动轨迹的定义
function w(s,c){
b.fillStyle = c;
b.fillRect(s % 20 * 20,~~(s / 20 * 20),18,18);
}
document.body.appendChild(c);
})();
蛇每吃掉一个食物,也就是往数组中添加一个 20*20
的小块,当然为了区分方向,这里的小块宽高应该与绘制的矩形宽高有关。因此,我们还要定义一个变量来代表蛇吃掉食物后,随机出现的下一个食物出现的位置。
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的,代表默认向右方向
d = 1,// 定义蛇活动方向,默认向右, 蛇运动方向为 s[1] - s[0]
f = 42,// 默认食物的位置
x;
// 这个函数既是绘制蛇方块,也是绘制食物与蛇活动轨迹的定义
function w(s,c){
b.fillStyle = c;
b.fillRect(s % 20 * 20,~~(s / 20 * 20),18,18);
}
document.body.appendChild(c);
})();
接下来就是用户按键盘的方向键,当然也可以是 wsad
字母键来控制蛇活动的方向。不过我们需要知道键盘键的 keyCode
, 方向键的keyCode
分别是:向左:37, 向上:38,, 向右:39, 向下:40
。键盘事件为onkeydown
。
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的,代表默认向右方向
d = 1,// 定义蛇活动方向,默认向右, 蛇运动方向为 s[1] - s[0]
f = 42,// 默认食物的位置
x;
// 这个函数既是绘制蛇方块,也是绘制食物与蛇活动轨迹的定义
function w(s,c){
b.fillStyle = c;
b.fillRect(s % 20 * 20,~~(s / 20 * 20),18,18);
}
// 按方向键控制蛇运动方向, 这里根据食物的位置来控制方向,防止用户随便更改方向,然后游戏崩溃
document.onkeydown = function(e){d = s[1] - s[0] === (x = [-1,-20,1,20][e || event].keyCode - 37 ] || d) ? d : x;
}
document.body.appendChild(c);
})();
然后蛇吃掉一个食物就应该添加下一个食物,在这里用 unshift()
方法来添加数组。
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的,代表默认向右方向
d = 1,// 定义蛇活动方向,默认向右, 蛇运动方向为 s[1] - s[0]
f = 42,// 默认食物的位置
x;
// 这个函数既是绘制蛇方块,也是绘制食物与蛇活动轨迹的定义
function w(s,c){
b.fillStyle = c;
b.fillRect(s % 20 * 20,~~(s / 20 * 20),18,18);
}
// 按方向键控制蛇运动方向, 这里根据食物的位置来控制方向,防止用户随便更改方向,然后游戏崩溃
document.onkeydown = function(e){// 方向由蛇头来确定,初始化蛇只有 2 个小方块组成,因此蛇的方向就是 s[1] - s[0]
d = s[1] - s[0] === (x = [-1,-20,1,20][e || event].keyCode - 37 ] || d) ? d : x;
}
!(function(){s.unshift(x = s[0] + d);
})();// 这也是一种自调用函数写法
document.body.appendChild(c);
})();
接下来判断蛇如果撞墙或者撞到了自身,则游戏结束。
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的,代表默认向右方向
d = 1,// 定义蛇活动方向,默认向右, 蛇运动方向为 s[1] - s[0]
f = 42,// 默认食物的位置
x;
// 这个函数既是绘制蛇方块,也是绘制食物与蛇活动轨迹的定义
function w(s,c){
b.fillStyle = c;
b.fillRect(s % 20 * 20,~~(s / 20 * 20),18,18);
}
// 按方向键控制蛇运动方向, 这里根据食物的位置来控制方向,防止用户随便更改方向,然后游戏崩溃
document.onkeydown = function(e){// 方向由蛇头来确定,初始化蛇只有 2 个小方块组成,因此蛇的方向就是 s[1] - s[0]
d = s[1] - s[0] === (x = [-1,-20,1,20][e || event].keyCode - 37 ] || d) ? d : x;
}
!(function(){s.unshift(x = s[0] + d);
// 判断蛇如果撞墙或者撞到了自身,则游戏结束
if(s.indexOf(x,1) > 0 || x < 0 || x > 399 || d == 1 && x % 20 == 0 || d == -1 && x % 20 == 19)return alert('游戏结束');
})();// 这也是一种自调用函数写法
document.body.appendChild(c);
})();
然后开始画食物,以及判断食物是否被蛇吃掉,如果吃掉了,则随机生成下一个食物。
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的,代表默认向右方向
d = 1,// 定义蛇活动方向,默认向右, 蛇运动方向为 s[1] - s[0]
f = 42,// 默认食物的位置
x;
// 这个函数既是绘制蛇方块,也是绘制食物与蛇活动轨迹的定义
function w(s,c){
b.fillStyle = c;
b.fillRect(s % 20 * 20,~~(s / 20 * 20),18,18);
}
// 按方向键控制蛇运动方向, 这里根据食物的位置来控制方向,防止用户随便更改方向,然后游戏崩溃
document.onkeydown = function(e){// 方向由蛇头来确定,初始化蛇只有 2 个小方块组成,因此蛇的方向就是 s[1] - s[0]
d = s[1] - s[0] === (x = [-1,-20,1,20][e || event].keyCode - 37 ] || d) ? d : x;
}
!(function(){s.unshift(x = s[0] + d);
// 判断蛇如果撞墙或者撞到了自身,则游戏结束
if(s.indexOf(x,1) > 0 || x < 0 || x > 399 || d == 1 && x % 20 == 0 || d == -1 && x % 20 == 19)return alert('游戏结束');
// 然后开始画蛇节点的颜色
w(x,'#e641d3');
// 判断蛇是不是吃到食物, 如果吃到则重新随机生成一个节点也就是新食物的坐标,Math.random()方法表示取随机数, 因为方向有可能是负的, 所以用到了~~ 符号表示取绝对值到正数.~ 就是先取反再减一的意思.
if(x == f){while (s.indexOf(f = ~~(Math.random() * 399)) > 0);
// 重新画食物颜色
w(f,'#35e3dc');
}else{
// 蛇吃到食物, 蛇身会变长, 所以不会改变蛇的运动轨迹
w(s.pop(),'#535353');
}
})();// 这也是一种自调用函数写法
document.body.appendChild(c);
})();
最后,让蛇按一定的时间运行,如下:
(function(){
// 创建 canvas 标签
var c = document.createElement('canvas');
c.width = 400;
c.height = 400;
c.style.background = '#535353';
var b = c.getContext('2d');
var s = [41,40],// 这里 41 也是有讲究的,代表默认向右方向
d = 1,// 定义蛇活动方向,默认向右, 蛇运动方向为 s[1] - s[0]
f = 42,// 默认食物的位置
x;
// 这个函数既是绘制蛇方块,也是绘制食物与蛇活动轨迹的定义
function w(s,c){
b.fillStyle = c;
b.fillRect(s % 20 * 20,~~(s / 20 * 20),18,18);
}
// 按方向键控制蛇运动方向, 这里根据食物的位置来控制方向,防止用户随便更改方向,然后游戏崩溃
document.onkeydown = function(e){// 方向由蛇头来确定,初始化蛇只有 2 个小方块组成,因此蛇的方向就是 s[1] - s[0]
d = s[1] - s[0] === (x = [-1,-20,1,20][e || event].keyCode - 37 ] || d) ? d : x;
}
!(function(){s.unshift(x = s[0] + d);
// 判断蛇如果撞墙或者撞到了自身,则游戏结束
if(s.indexOf(x,1) > 0 || x < 0 || x > 399 || d == 1 && x % 20 == 0 || d == -1 && x % 20 == 19)return alert('游戏结束');
// 然后开始画蛇节点的颜色
w(x,'#e641d3');
// 判断蛇是不是吃到食物, 如果吃到则重新随机生成一个节点也就是新食物的坐标,Math.random()方法表示取随机数, 因为方向有可能是负的, 所以用到了~~ 符号表示取绝对值到正数.~ 就是先取反再减一的意思.
if(x == f){while (s.indexOf(f = ~~(Math.random() * 399)) > 0);
// 重新画食物颜色
w(f,'#35e3dc');
}else{
// 蛇吃到食物, 蛇身会变长, 所以不会改变蛇的运动轨迹
w(s.pop(),'#535353');
}
// 这是一种递归的写法
setTimeout(arguments.callee,300);
})();// 这也是一种自调用函数写法
document.body.appendChild(c);
})();
到此为止,就拆分完了,其实这里的逻辑不算难,难的是计算蛇与蛇运动轨迹还有食物的坐标。能够理解透,就看个人的数学知识了,哈哈。最后,将这么多代码整合成一行代码,就可以好好的装逼了!
ps
:
鄙人创建了一个 QQ 群,供大家学习交流,希望和大家合作愉快,互相帮助,交流学习,以下为群二维码: