前言
以前读书学习C语言的时候,总是想着要完成贪食蛇这款游戏,但是那时候我的编程基础和编程思维还不够扎实和成熟,所以导致怎么折腾都写不出来,但是现在工作了,就想着要完成读书时候的小心愿,就开始动手写了,写着写着,就还真是写出来了,哈哈,看来我也是成长了不少。。。
贪食蛇这款游戏,相信大家一定都不陌生了,童年之作,用编程语言编写出来的贪食蛇,无非就是要实现几个功能:蛇的移动、是否吃到自己、吃到食物、撞墙,只要把需要实现的功能给列出来了,接下来只要一个个去实现就好了,思路清晰还是很重要的,因为都是用原生JS来编写的,所以对JS功底还是有点要求的,我觉得有时间练手的可以尝试一下,好了,废话不多说,进去正题。。。
分析思路
首先我们需要创建一个大画布,开始和暂停按钮,画布是蛇在里面跑的,开始和暂停分别控制开始和停止,这里需要用到HTML和CSS,但我选择了用JS实现,毕竟练手嘛,当然是要往不同的方法去出发。
然后动态创建蛇的节点(一个蛇头两个蛇点)和食物,好了,最基本的HTML和CSS已经用JS来实现了,接下来开始实现需要实现的功能了
- 蛇的移动
- 撞墙的判断
- 是否吃到食物
- 是否吃到自己
好了,思路分析好了就开始动手了!!
Let't it go ..
场景准备
如上所述,首先要创建场景,画布和开始、暂停按钮
直接上代码:
let d = document,
//定时器
timer = null,
//禁止连续按键
disabled = 0,
//main是画布,open是开始,stop是暂停
node = {
main: d.createElement('div'),
open: d.createElement('button'),
stop: d.createElement('button'),
}
//赋予样式
node.main.style.background = `rgb(104, 163, 210)`;
node.main.style.width = `${15 * (30+1)}px`;
node.main.style.height = `${15 * (20+1)}px`;
node.main.id = 'main';
node.main.position = `relative`;
node.open.textContent = `开始`;
node.stop.textContent = `暂停`;
//加入document.body
for( key in node) document.body.appendChild(node[key]);
这样就创建好画布和按钮了,完成了基本的场景准备
创建蛇节点
这里我本来打算用函数function Snake(),然后通过构造函数去调用的,但想了想,但是用ES6的class去定义吧!!
直接上代码:
class Snake {
constructor() {
this.init();
}
//初始化
init() {
//添加蛇身汇总
this.snakeAll = document.createElement('div');
this.snakeAll.id = 'all';
this.snakeAll.style.position = `relative`;
this.snakeAll.style.top = `${0}px`;
document.getElementById('main').appendChild(this.snakeAll);
//蛇节点坐标
this.Snode = [
{ x: 3 , y: 1, flag: null},
{ x: 2 , y: 1, flag: null},
{ x: 1 , y: 1, flag: null},
];
//默认方向
this.direction = 'right';
//蛇节点宽度
this.width = 15;
//蛇节点长度
this.height = 15;
this.createSnake();
}
}
new Snake()的时候调用构造函数,调用this.init(),对蛇需要用到的参数进行初始化和创建蛇必要的节点
接下来看this.createSnake()代码
//创建蛇
createSnake() {
//首先清除蛇的节点
this.removeSnake();
//根据坐标添加节点
this.Snode.forEach( item => {
if(item.x != null) {
let div = document.createElement('div');
div.style.background = `#734fb3`;
div.style.width = `${this.width}px`
div.style.height = `${this.width}px`
div.style.position = `absolute`;
div.style.left = `${item.x * this.width}px`
div.style.top = `${item.y * this.height}px`
div.style.borderRadius = `${50}%`
div.classList.add('snake');
item.flag = div;
this.snakeAll.appendChild(div);
}
})
}
this.createSnake()也是class Snake {}里面的方法,只是我怕代码太长你们看的不耐烦...
this.createSnake()的作用就是根据蛇的默认参数来创建蛇的节点,创建之前需要清除之前存在的蛇的节点
看this.removeSnake()代码:
//删除蛇
removeSnake() {
for( let key in this.Snode) {
this.Snode[key].flag && this.snakeAll.removeChild(this.Snode[key].flag)
}
}
好了,创建蛇的节点的逻辑大概就是这样,这里来总结一下:
通过定义Snake的类来编写方法,new Snake()的时候会调用构造函数里面的this.init()来设置默认参数
然后调用this.createSnake()来根据this.Snode[]默认参数来创建蛇的节点
创建之前一定要记住调用this.removeSnake()来清除之前创建的节点,这部分的逻辑大概就是如此。。。
创建食物
创建食物的逻辑比较简单吧,跟创建蛇的逻辑是差不多的
也是定义一个Food的类,然后通过new Food()去调用创建食物
好了,直接上代码
class Food {
constructor() {
this.width = 15;
this.height = 15;
this.createFood();
}
//创建食物
createFood() {
this.food && this.food.remove();
this.food = document.createElement('div');
this.food.id = 'food';
this.x = Math.floor(Math.random() * 15);
this.y = Math.floor(Math.random() * 10);
this.food.style.position = `relative`;
this.food.style.top = `${this.width * this.y}px`;
this.food.style.left = `${this.height * this.x}px`;
this.food.style.width = `${this.width}px`
this.food.style.height = `${this.height}px`
this.food.style.background = `#3F51B5`
this.food.style.borderRadius = `${50}%`
document.getElementById('all').appendChild(this.food);
}
}
看了上面创建蛇的逻辑,再看这段创建食物的逻辑应该问题不大吧?哈哈,我相信你看懂了
蛇的移动
接下来开始说蛇的移动了,这段是重点呢,因为蛇的移动包含了撞墙的判断、吃到食物的判断、吃到自己的判断等功能,是有点麻烦,但是我会分段来说,这样会清晰很多,好了,直接开始...
蛇的移动的原理就是除了蛇头,蛇尾的后一个点的坐标等于前一个点的坐标,蛇头的坐标由this.direction这个属性来控制的,然后通过计时器不断执行this.createSnake()就可以实现蛇的移动了
好了,直接上代码
//运动
run(food,timer) {
this.food = food;
//移动蛇身体
for(let i=this.Snode.length-1; i>0; i--) {
this.Snode[i].x = this.Snode[i-1].x;
this.Snode[i].y = this.Snode[i-1].y;
}
//移动蛇头
switch(this.direction){
case 'left': this.Snode[0].x -=1;break;
case 'right': this.Snode[0].x += 1; break;
case 'top': this.Snode[0].y -= 1; break;
case 'bottom': this.Snode[0].y += 1;break;
}
this.createSnake();
}
这样子就可以实现蛇的移动了,是不是很简单呢
下面开始细分功能,以下的代码都是在run()方法里面的,我把他给分出来这样容易看清楚
判断是否撞墙
判断是否撞墙就是判断蛇的X,Y坐标是否超出画布的最大或最小X、Y坐标
上面的代码说了
画布X的坐标范围是0 - 30
画布Y的坐标范围是0 - 20
//判断是否撞墙,根据蛇头的X和Y坐标去判断
if(this.Snode[0].x > 30 || this.Snode[0].x < 0 || this.Snode[0].y > 20 || this.Snode[0].y < 0) {
alert("哎哟,老兄,看路啊,把我给撞晕了");
this.snakeAll.remove();
clearInterval(timer);
this.init();
this.food.createFood();
}
撞墙后移动蛇的全部节点,清除定时,重新调用this.init()方法创建,和调用this.food.createFood()方法创建食物
是不是条例很清晰呢?
判断吃到食物
吃到食物的逻辑也是很简单的
当蛇头的坐标X和Y都等于食物的X和Y就等于是吃到了食物...
//吃到食物
if(this.Snode[0].x == this.food.x && this.Snode[0].y == this.food.y) {
this.Snode.push({x: null,y: null,flag: null});
this.food.createFood();
}
this.Snode控制蛇的节点,吃到食物后需要多增加一个节点,直接push()进去就好
然后重新调用方法创建食物就好
判断是否吃到自己
自己吃到自己,这种情况只会在蛇拥有四个节点以上的情况下才会发生
当蛇头的坐标等于自己身体的任意一个坐标的时候,就可以判断为吃到了自己
//判断是否吃到自己,只有四个点以上才有可能吃到自己
for(let i=4;i<this.Snode.length;i++) {
if(this.Snode[0].x == this.Snode[i].x && this.Snode[0].y == this.Snode[i].y) {
alert("哎哟,老兄,肚子饿也不能吃自己啊");
this.snakeAll.remove();
clearInterval(timer);
this.init();
this.food.createFood();
}
}
吃到自己后,也是需要初始化和重新创建食物
好了,看到这里,基本上已经完成了,接下来只需要添加点DOM事件即可
添加事件
添加键盘事件和开始、暂停定时事件
//添加键盘事件
document.body.onkeydown = e => {
let ev = e || window.event;
if(!disabled) {
disabled = 1;
switch(ev.keyCode){
case 39:{
if(snake.direction != 'left'){
snake.direction = 'right';
break;
}
}
case 38:{
if(snake.direction != 'bottom'){
snake.direction = 'top';
break;
}
}
case 37:{
if(snake.direction != 'right'){
snake.direction = 'left';
break;
}
}
case 40:{
if(snake.direction != 'top'){
snake.direction = 'bottom';
break;
}
}
};
}
setTimeout( () => {
disabled = 0;
},100);
}
//开始
node.open.addEventListener('click', ()=> {
clearInterval(timer);
timer = setInterval( () => {
snake.run(food,timer);
},100);
})
//暂停
node.stop.addEventListener('click', ()=> {
clearInterval(timer);
})
好了,代码写到这里基本上就完成了,不知道你们有没有看到最后(伤心脸)!!
完整代码
电脑上重装了,没了git,懒得装,直接贴完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
class Snake {
constructor() {
this.init();
}
//初始化
init() {
//添加蛇身汇总
this.snakeAll = document.createElement('div');
this.snakeAll.id = 'all';
this.snakeAll.style.position = `relative`;
this.snakeAll.style.top = `${0}px`;
document.getElementById('main').appendChild(this.snakeAll);
//蛇节点坐标
this.Snode = [
{ x: 3 , y: 1, flag: null},
{ x: 2 , y: 1, flag: null},
{ x: 1 , y: 1, flag: null},
];
//默认方向
this.direction = 'right';
//蛇节点宽度
this.width = 15;
//蛇节点长度
this.height = 15;
this.createSnake();
}
//创建蛇
createSnake() {
//首先清除蛇的节点
this.removeSnake();
//根据坐标添加节点
this.Snode.forEach( item => {
if(item.x != null) {
let div = document.createElement('div');
div.style.background = `#734fb3`;
div.style.width = `${this.width}px`
div.style.height = `${this.width}px`
div.style.position = `absolute`;
div.style.left = `${item.x * this.width}px`
div.style.top = `${item.y * this.height}px`
div.style.borderRadius = `${50}%`
div.classList.add('snake');
item.flag = div;
this.snakeAll.appendChild(div);
}
})
}
//删除蛇
removeSnake() {
for( let key in this.Snode) {
this.Snode[key].flag && this.snakeAll.removeChild(this.Snode[key].flag)
}
}
//运动
run(food,timer) {
this.food = food;
//移动蛇身体
for(let i=this.Snode.length-1; i>0; i--) {
this.Snode[i].x = this.Snode[i-1].x;
this.Snode[i].y = this.Snode[i-1].y;
}
//移动蛇头
switch(this.direction){
case 'left': this.Snode[0].x -=1;break;
case 'right': this.Snode[0].x += 1; break;
case 'top': this.Snode[0].y -= 1; break;
case 'bottom': this.Snode[0].y += 1;break;
}
//判断是否撞墙,根据蛇头的X和Y坐标去判断
if(this.Snode[0].x > 30 || this.Snode[0].x < 0 || this.Snode[0].y > 20 || this.Snode[0].y < 0) {
alert("哎哟,老兄,看路啊,把我给撞晕了");
this.snakeAll.remove();
clearInterval(timer);
this.init();
this.food.createFood();
}
//吃到食物
if(this.Snode[0].x == this.food.x && this.Snode[0].y == this.food.y) {
this.Snode.push({x: null,y: null,flag: null});
this.food.createFood();
}
//判断是否吃到自己,只有四个点以上才有可能吃到自己
for(let i=4;i<this.Snode.length;i++) {
if(this.Snode[0].x == this.Snode[i].x && this.Snode[0].y == this.Snode[i].y) {
alert("哎哟,老兄,肚子饿也不能吃自己啊");
this.snakeAll.remove();
clearInterval(timer);
this.init();
this.food.createFood();
}
}
this.createSnake();
}
}
class Food {
constructor() {
this.width = 15;
this.height = 15;
this.createFood();
}
//创建食物
createFood() {
this.food && this.food.remove();
this.food = document.createElement('div');
this.food.id = 'food';
this.x = Math.floor(Math.random() * 15);
this.y = Math.floor(Math.random() * 10);
this.food.style.position = `relative`;
this.food.style.top = `${this.width * this.y}px`;
this.food.style.left = `${this.height * this.x}px`;
this.food.style.width = `${this.width}px`
this.food.style.height = `${this.height}px`
this.food.style.background = `#3F51B5`
this.food.style.borderRadius = `${50}%`
document.getElementById('all').appendChild(this.food);
}
}
(()=>{
let d = document,
//定时器
timer = null,
//禁止连续按键
disabled = 0,
//main是画布,open是开始,stop是暂停
node = {
main: d.createElement('div'),
open: d.createElement('button'),
stop: d.createElement('button'),
}
//赋予样式
node.main.style.background = `rgb(104, 163, 210)`;
node.main.style.width = `${15 * (30+1)}px`;
node.main.style.height = `${15 * (20+1)}px`;
node.main.id = 'main';
node.main.position = `relative`;
node.open.textContent = `开始`;
node.stop.textContent = `暂停`;
//加入document.body
for( key in node) document.body.appendChild(node[key]);
let snake = new Snake();
let food = new Food();
//添加键盘事件
document.body.onkeydown = e => {
let ev = e || window.event;
if(!disabled) {
disabled = 1;
switch(ev.keyCode){
case 39:{
if(snake.direction != 'left'){
snake.direction = 'right';
break;
}
}
case 38:{
if(snake.direction != 'bottom'){
snake.direction = 'top';
break;
}
}
case 37:{
if(snake.direction != 'right'){
snake.direction = 'left';
break;
}
}
case 40:{
if(snake.direction != 'top'){
snake.direction = 'bottom';
break;
}
}
};
}
setTimeout( () => {
disabled = 0;
},100);
}
//开始
node.open.addEventListener('click', ()=> {
clearInterval(timer);
timer = setInterval( () => {
snake.run(food,timer);
},100);
})
//暂停
node.stop.addEventListener('click', ()=> {
clearInterval(timer);
})
})();
</script>
</body>
</html>
本人是菜鸟,如果觉得我那里写得不好,可以及时指出来,我也很乐意接受高手的指点,哈哈!!
对了 对了 忘了贴效果图
难看是难看了点,但重要的是逻辑思维嘛,哈哈!!
总结
我觉得写下了这个贪食蛇,是对自己的编程实力和思维是一种检测,虽然没有什么实际性的意义(尴尬脸)
不知不觉我也已经工作一年多了,从毕业的跨行成功到现在生活的无奈,有时候我也不知道坚持是为了什么,我也似乎是忘记了自己的初心了,没错,我就是那种很丧很丧的那种人,哈哈!!
最后送大家一句话:人生,无非就是起起落落落落落落,所以要好好活着,哈哈,生活,加油!!