简易贪吃蛇游戏制作完成
代码:
food.js
class Food{
constructor(options){
options = options || {}
this.food = null
this.map = document.querySelector('.map')
this.width = options.width || 20
this.height = options.height || 20
this.x = 0
this.y = 0
// this.bgColor = options.color || 'pink'
this.init()
}
init(){
// 创建之前 先判断 页面有没有 .food 如果有 就先干掉
document.querySelector('.food') ? this.map.removeChild(document.querySelector('.food')) : ''
// 创建食物 放到地图当中去
this.food = document.createElement('p')
this.food.className = 'food'
css(this.food,'width',this.width + 'px')
css(this.food,'height',this.height + 'px')
css(this.food,'background-color',getColor())
css(this.food,'position','absolute')
// 总共 可以吧地图分为 800 / 20 个网格
// 要设置的食物 的 left = 网格 * 20 left 的最大值 为 780 所以网格最多只能有 800 / 20 - 1 个
// 求 食物的位置
// 随机 0 - 39 个网格之间 网格 * 食物的宽度 = 我要设置的 left 值
// 加入 地图的宽度 是 798 / 20
this.x = getRandom(0,Math.floor(this.map.offsetWidth / this.width - 1))
this.y = getRandom(0,Math.floor(this.map.offsetHeight / this.height - 1))
// 要设置的 left 值 = 0 - count 之间的随机数 * this.width
css(this.food,'left',this.x * this.width + 'px')
css(this.food,'top',this.y * this.height + 'px')
this.map.appendChild(this.food)
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- <div class="map"></div> -->
<button id="btn">一起动</button>
<!-- 乌梢蛇 地图 食物 -->
<script src="./utils.js"></script>
<script src="./map.js"></script>
<script src="./food.js"></script>
<script src="./snake.js"></script>
<script src="./main.js"></script>
<script>
new Main()
</script>
</body>
</html>
main.js
class Main {
constructor(btn) {
this.btn = btn || document.querySelector("#btn")
this.map = new Map()
this.food = new Food()
this.snake = new Snake({},this.food)
this.startRun()
this.keydownEvent()
}
keydownEvent() {
on(document, 'keydown', (e) => {
// console.log(e.keyCode)
// 需要通过 按钮 去改变 snake 文件里面的 方向
const code = e.keyCode
switch (code) {
case 37:
this.snake.direction != 'right' && (this.snake.direction = 'left')
break
case 38:
this.snake.direction != 'down' && (this.snake.direction = 'up')
break
case 39:
this.snake.direction != 'left' && (this.snake.direction = 'right')
break
case 40:
this.snake.direction != 'up' && (this.snake.direction = 'down')
break
}
// console.log(this.snake)
})
}
startRun(){
on(this.btn,'click',() => {
this.snake.move()
})
}
}
map.js
class Map{
constructor(options){
options = options || {}
this.map = options.map || null
this.width = options.width || 800
this.height = options.height || 600
this.bgColor = options.bgColor || '#333'
this.init()
}
// 要有个方法 创建这个地图
init(){
this.map = document.createElement('div')
this.map.className = 'map'
// 设置样式
css(this.map,'width',this.width + 'px')
css(this.map,'height',this.height + 'px')
css(this.map,'background-color',this.bgColor)
css(this.map,'margin','100px auto')
css(this.map,'position','relative')
// console.log(this.map)
// 追加到页面
document.body.appendChild(this.map)
}
}
map.js
class Snake {
constructor(options,food) {
options = options || {}
this.food = food
this.width = options.width || 20
this.height = options.height || 20
this.map = document.querySelector('.map')
this.direction = 'right'
this.timer = null
this.body = [
{
x: 3,
y: 2,
ele: document.createElement('div'),
color: '#0f0'
},
{
x: 2,
y: 2,
ele: document.createElement('div'),
color: 'pink'
}, {
x: 1,
y: 2,
ele: document.createElement('div'),
color: 'pink'
}
]
this.init()
}
init() {
this.body.forEach(item => {
// console.log(item)
css(item.ele, 'width', this.width + 'px')
css(item.ele, 'height', this.height + 'px')
css(item.ele, 'background-color', item.color)
css(item.ele, 'position', 'absolute')
css(item.ele,'borderRadius','50%')
css(item.ele, 'left', item.x * this.width + 'px')
css(item.ele, 'top', item.y * this.height + 'px')
this.map.appendChild(item.ele)
})
}
move() {
this.timer = setInterval(() => {
// 我要遍历数组 让数组里面后一节身体 = 前一节身体的坐标
for (let i = this.body.length - 1; i > 0; i--) {
this.body[i].x = this.body[i - 1].x
this.body[i].y = this.body[i - 1].y
}
// console.log(this.body)
// 让 头 往 右边走
// 只有当 方向 为 right 的时候 才执行 this.body[0].x++
const head = this.body[0]
switch (this.direction) {
case 'right':
head.x++
break
case 'left':
head.x--
break
case 'up':
head.y--
break
case 'down':
head.y++
break
}
this.body.forEach(item => {
css(item.ele, 'left', item.x * this.width + 'px')
css(item.ele, 'top', item.y * this.height + 'px')
})
// 边界判断
if(head.x < 0 || head.x > Math.floor(this.map.offsetWidth / this.width) - 1 || head.y <0 || head.y > Math.floor(this.map.offsetHeight / this.height) - 1){
alert('gameover')
clearInterval(this.timer)
}
// 判断是否吃到食物 头的坐标是否和食物重合
if(head.x === this.food.x && head.y === this.food.y){
// 吃到食物以后 食物重新生成一次
this.food.init()
// 蛇 添加一节 this.body
this.body.push({
x: this.body[this.body.length - 1].x,
y: this.body[this.body.length - 1].y,
ele: document.createElement('div'),
color: getColor()
})
this.init()
}
}, 100)
}
}
utils.js
*
工具类函数
*/
/*
检测一个字符串是否是可回文字符串
@param a 要检测的字符串
@return 返回值是一个布尔值
*/
function fn(a) {
return a.split('').reverse().join('') === a ? true : false
}
/*
求一个范围之间的随机数
@param a 范围下限
@param b 范围上限
@return 范围之间的随机数
*/
function getRandom(a, b) {
return Math.floor(Math.random() * (b - a + 1) + a)
}
/*
随机颜色
@return 随机颜色字符串
*/
function getColor() {
return "rgb(" + getRandom(0, 255) + "," + getRandom(0, 255) + "," + getRandom(0, 255) + ")"
}
/*
通过选择器获取元素
@param selector 要获取的元素的 id/class/标签名
@param context 从哪个范围获取
@return 获取到的元素或者元素伪数组
*/
function my$(selector, context) {
context = context || document
if (selector.indexOf('#') === 0) {
return document.getElementById(selector.slice(1))
}
if (selector.indexOf('.') === 0) {
return context.getElementsByClassName(selector.slice(1))
}
return context.getElementsByTagName(selector)
}
/*
获取/设置元素的样式
@param ele 元素
@param attr 要获取/设置的属性
@param value 要获取/设置的属性值
@return 获取/设置好的属性值
*/
function css(ele, attr, value) {
if (value) {
ele.style[attr] = value
}
// 有第三个参数就是设置 没有就是获取
return window.getComputedStyle ? window.getComputedStyle(ele)[attr] : ele.currentStyle[attr]
}
/*
绑定事件 兼容处理
@param ele 要绑定事件的元素
@param type 事件类型
@param fn 事件处理函数
*/
function on(ele, type, fn) {
if (ele.addEventListener) {
if (type.indexOf('on') === 0) {
type = type.slice(2)
}
ele.addEventListener(type, fn)
} else {
if (type.indexOf('on') !== 0) {
type = 'on' + type
}
ele.attachEvent(type, fn)
}
}
function off(){
}
/*
对 ie8 不支持 pageX pageY 做的兼容处理
@param e event 对象
@return 光标距离页面左边和上边的距离数据对象
*/
function page(e) {
if (e.pageX) {
return { x: e.pageX, y: e.pageY }
}
var _x = document.documentElement ? document.documentElement.scrollLeft + e.clientX : document.body.scrollLeft + e.clientX
var _y = document.documentElement ? document.documentElement.scrollTop + e.clientY : document.body.scrollTop + e.clientY
return { x:_x,y:_y}
}
/*
运动框架函数
@param ele 执行运动的元素
@param options 终点值 是一个对象
@param duration 运动的总时间
@param fn 运动执行完毕的回调函数
*/
function animate(ele, options, duration,fn) {
clearInterval(ele.timer)
const start = {}, range = {}
for (var key in options) {
start[key] = parseFloat(css(ele, key))
range[key] = options[key] - start[key]
}
const startTime = +new Date()
ele.timer = setInterval(() => {
let nowTime = +new Date()
let timeDifference = Math.min(nowTime - startTime,duration)
for (let attr in options) {
let result = start[attr] + range[attr] / duration * timeDifference
result = attr === 'opacity' ? result : result + 'px'
ele.style[attr] = result
}
if (timeDifference === duration) {
clearInterval(ele.timer)
fn && fn()
}
}, 10)
}
/*
淡入
*/
function fadeIn(ele,time,fn){
css(ele,'display','block')
css(ele,'opacity','0')
animate(ele,{opacity:1},time,fn)
}
/*
淡出
*/
function fadeOut(ele,time,fn){
css(ele,'display','block')
css(ele,'opacity','1')
animate(ele,{opacity:0},time,fn)
}