// 面向对象思想,利用对象解决贪吃蛇
//1.地图
//2.食物
//3.蛇
map.js
//利用面向对象思想创建地图
class Map{
constructor(){
//创建地图
let oMap = document.createElement("div");
//设置地图
oMap.className = "map_all"
/* oMap.style.position = relative; */
//把地图添加到页面中
document.body.appendChild(oMap);
//拿到其中属性
let oMapStyle = window.getComputedStyle(oMap)
this.oMap = oMap;
this.width = oMapStyle.width
this.height = oMapStyle.height
}
}
//只需创建Map的实例对象,即可创建一个地图
snake.js
//Snake:1.创建蛇各部分并渲染到页面
// 2.
class Snake{
constructor(obj){
let snake_all = [
{x:2,y:1,type:1},
{x:1,y:1,type:0},
{x:0,y:1,type:0},
]
//没有设置就使用默认值,注意obj默认值是空对象必须写,否则下方设置没用,因为均是设置给obj的属性
obj = obj || { }
this.width = obj.width || 100
this.height = obj.height || 100
this.bodyimg = obj.bodyimg || "images/body.png"
this.headimg = obj.headimg || "images/head.png"
this.snake_all = snake_all;
this.map = obj.map || {}
//键盘按键按下事件,仅执行一次,作为蛇头移动的参考
document.body.onkeydown = (event)=>{
//由于下方需要蛇头移动的按键,作为属性传入key
//使用es6箭头函数,解决this为body添加不到构造函数属性的问题,使用箭头函数this即为当前的父作用域
this.key = event.key;
};
//判断边界最大数值使用
let widthNumber = parseInt(map.width) / parseInt(this.width);
let heightNumber = parseInt(map.height) / parseInt(this.height);
this.widthNumber = widthNumber;
this.heightNumber = heightNumber;
}
creatSnake(map){
//由于每次移动后都要重新渲染,因此要将以前的蛇部分全部清除,为了方便操作,为其蛇部分添加类名标识,querySelectorAll找到所有的
let snake_parts = document.querySelectorAll('.snake_parts')
//console.log(snake_parts),不加定时器默认输出两次,而且它返回的是一个数组,使用for of遍历
for(let value of snake_parts){
value.parentNode.removeChild(value)
}
//同样的,数组也要变为属性,要不原型对象中找不着
for(let value of this.snake_all){
//for of 方法专门遍历数组并进行操作
//1.创建每一部分对应的div
let oDiv = document.createElement('div')
//2.设置蛇属性
oDiv.style.width = this.width +"px"
oDiv.style.height = this.height +"px"
//3.设置蛇每部分的位置,前提设置poa,否则没法设置偏移量
oDiv.style.position = "absolute"
oDiv.style.left = value.x * parseInt(this.width) + "px";
oDiv.style.top = value.y * parseInt(this.height) + "px";
//4.设置蛇每部分对应背景
if(value.type==1){ //都写到模板字符串内,不要 url(` `)
oDiv.style.background = `url(${this.headimg})`
}
else {
oDiv.style.background = `url(${this.bodyimg})`
}
//5.将蛇的每一部分均添加到地图中
map.oMap.appendChild(oDiv)
//6.为蛇每部分添加类名标识,便于找到遍历删除
oDiv.className="snake_parts"
}
}
move(){
//1. 移动蛇身,后一个身体组件的位置移动到前一个身体组件的位置,则最前方与头重叠,单独设置头移动即可,因此从后往前循环
for(let i=this.snake_all.length-1;i>0;i--){
//这里的移动不包括蛇头
//后一个位置x,y移到前一个的位置x,y,调整的是x,y而非整个部分
this.snake_all[i].x = this.snake_all[i-1].x
this.snake_all[i].y = this.snake_all[i-1].y
//要单独设置调整后的type,因为最前面的蛇身体与蛇头重叠了
this.snake_all[i].type = 0;
}
//2. 移动蛇头,根据按键方向来调整蛇头方向与位置,因此需要先获取到蛇头,而且不能写到最上方,因为蛇头每次都在变化,必须写到定时器内
let snakehead = this.snake_all[0]
//console.log(this.key)undefiend,是因为上方按键事件用的es5,this为body,而不是整个构造函数,因此么有值,蛇头也动不了,因此换成箭头函数
switch(this.key){
//向上
case "w": snakehead.y = snakehead.y-1
break;
//向下
case "s": snakehead.y = snakehead.y+1
break;
//向左
case "a": snakehead.x = snakehead.x-1
break;
//向右
case "d": snakehead.x = snakehead.x+1
break;
default: snakehead.x = snakehead.x+1
break;
}
}
inspection(snakefood){
//检测边界代码
//3.判断蛇头是否超过边界,没有超过继续定时器内删除创建,超过不渲染蛇,并清除定时器
let snakehead = this.snake_all[0]
if(snakehead.x<0||snakehead.y<0||snakehead.x>=this.widthNumber||snakehead.y>=this.heightNumber){
alert("不要超出挑战区,否则xz鸽鸽去找你玩耍~")
clearInterval(this.timer)
//超出边界了不绘制,不超出默认在外部返回true;
return false;
}
//5.每次移动时都要判断是否吃到了豆豆(蛇头x,y与豆豆一样),吃到了要变长一截,然后把场上的豆豆删掉,重新渲染一个豆豆;
//console.log(snakefood.oFood.style.left,snakefood.oFood.style.top,snakehead.x,snakehead.y)
// 700px 400px 7 4 因此要进行判断,可以先取整再/长度 .即为 7 4 7 4
if(snakehead.x===parseInt(snakefood.oFood.style.left)/this.width && snakehead.y===parseInt(snakefood.oFood.style.top)/this.height ){
//吃到了就立即删除场上的食物,并重新渲染一个食物出来,怎么删除怎么渲染只有食物知道,进入食物设置方法
//删除场上食物
snakefood.removeFood()
//重新渲染食物
snakefood.xuanran(map)
//吃到了,新建一节蛇身躯放到最后
//拿到最后一节的位置
let lastBody = this.snake_all[this.snake_all.length-1]
//将最后一节位置的x,y赋给新增到最后的数组,此时最后一个与新增的重叠了,因此要以最后一个为依据,单独处理最后一节身躯的x,y。
let newBody = {x:lastBody.x,y:lastBody.y,type:0}
switch(this.key){
//根据朝向,设置最后一节的位置
//向上
case "w": newBody.y = newBody.y + 1
break;
//向下
case "s": newBody.y = newBody.y - 1
break;
//向左
case "a": newBody.x = newBody.x + 1
break;
//向右
case "d": newBody.x = newBody.x - 1
break;
default: newBody.x = newBody.x - 1
break;
}
//将调整好位置的最后一节身躯添加到数组snake_all中,放到蛇身体里,利用push方法放到最后,可以一直拿到最后新的数值,即可循环起来
this.snake_all.push(newBody)
}
//没有超出边界默认返回true
return true;
}
updata(map,snakefood){
//定时器要定义同样为属性,否则clear删除不掉,直接给名不好使
this.timer = setInterval(() => {
//1.让蛇移动起来
this.move()
//2.检测边界与食物,单独设置inspection方法,在updata总方法内这里会给inspection传递snakefood参数,这样照样执行this.inspection(snakefood)
let flag = this.inspection(snakefood)
if(!flag){
//如果flag为假,即超出了边界,则不绘制蛇了,不往下面执行creatSnake,直接return退出
return;
}
//3.蛇的位置变了,重新渲染蛇的每一部分,仍需要map,可以在上面的方法将其作为构造函数的一个属性,构造函数内的所有方法即可使用map
this.creatSnake(map)
}, 500);
}
}
snakefood.js
//利用面向对象思想创建随机生成的食物
class SnakeFood{
constructor(){
//创建食物div
let oFood = document.createElement("div")
//设置食物样式
oFood.className = "oFood";
oFood.style.width = 100+"px"
oFood.style.height = 100+"px"
//获取当前食物div宽高
//style只能获取行内样式,window.getComputedStyle返回元素的所有非行内样式,class
// let oFoodstyle = window.getComputedStyle(oFood)
// oFoodstyle.width oFoodstyle.height 均为空 ,创造的实例中对应属性也为空,获取不到,用js设置可以拿到
this.width1 = oFood.style.width
this.height1 = oFood.style.height
this.oFood = oFood
}
xuanran(map){
//随机生成位置,为其设置位置,将设置好的位置添加到地图中去。
//1.查看地图中最多容纳几个食物
//同理,想拿到map.width,也不能直接style拿,都是用非行内,window.getComputedStyle,发现报错,拿不到this.width1,用js
let widthNumber = parseInt(map.width) / parseInt(this.width1);
let heightNumber = parseInt(map.height) / parseInt(this.height1);
this.widthNumber = widthNumber;
this.heightNumber = heightNumber;
//2.随机生成 (0 至 最大容纳数-1)的数值,因为最终位置的改变是设置食物的left,top而决定的,0才是第一个坑位,用随机数*宽度高度赋值给偏移量即可
//用同一个构造函数中原型对象的其他方法要用this.方法,设置left,top最后要加上px
this.oFood.style.left = parseInt(this.width1) * this.getwidthrandom()+"px"
this.oFood.style.top = parseInt(this.height1) * this.getheightrandom()+"px"
//添加到地图中,注意这里用保存给map属性时,不要 map.this.属性,直接map.属性用就行
//不同方法内,ES6此处写在其构造函数的原型对象中,自然找不到oFood,也要在上方添加属性即可解决问题。
map.oMap.appendChild(this.oFood);
}
getwidthrandom(){
return Math.floor(Math.random() * ((this.widthNumber-1) - 0)) + 0; //不含最大值,含最小值
}
getheightrandom(){
return Math.floor(Math.random() * ((this.heightNumber-1) - 0)) + 0; //不含最大值,含最小值
}
removeFood(){
//拿到食物div并删除他
this.oFood.parentNode.removeChild(this.oFood)
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<Meta http-equiv="X-UA-Compatible" content="IE=edge">
<Meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./css/index.css">
<script src="./js/index.js"></script>
<script src="./js/map.js"></script>
<script src="./js/SnakeFood.js"></script>
<script src="./js/snake.js"></script>
</head>
<body>
<!-- <div class="map_all"></div> -->
<script>
//注意,这些创建实例均为js操作,写到script标签里;
//创建地图
let map = new Map()
//创建食物
let snakefood = new SnakeFood();
//随机生成食物并渲染到地图中,并记住,谁需要就放到哪个方法的参数中去
snakefood.xuanran(map)
//创建蛇
let obj = {
//这里不要加单位,因为creatSnake方法内拼接了 “px”
width:100,
height:100,
bodyimg:"images/body.png",
headimg:"images/head.png",
map:map
}
let snake = new Snake(obj)
//创建蛇身体的每个部分并渲染到地图中。
snake.creatSnake(map)
//蛇移动
snake.updata(map,snakefood)
</script>
</body>
</html>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。