class Rect {
x = 0
y = 0
width = 0
height = 0
constructor({ x, y, width = 10, height = 10 }){
this.x = x
this.y = y
this.width = width
this.height = height
}
}
class Body {
rect = null
div = null
constructor(rect){
this.render(rect)
}
render(rect){
this.createBody()
this.setRect(rect)
}
// 創(chuàng)建舍身
createBody(){
this.div = document.createElement('div');
this.div.setAttribute('class', 'snake_body');
document.body.appendChild(this.div);
}
// 位置設(shè)置
setRect(rect){
this.rect = rect
this.div.style.top = rect.y + 'px'
this.div.style.left = rect.x + 'px'
this.div.style.width = rect.width + 'px'
this.div.style.height = rect.height + 'px'
}
}
class Food {
rect = null
div = null
size = null
constructor(){
this.render()
}
// 類方法,創(chuàng)建隨機(jī)食物
static scatterFoods(){
let foods = []
let count = 0;
while(count < 100) {
// 要執(zhí)行的操作
foods.push(new Food())
count++;
}
return foods;
}
// 渲染
render(){
this.size = (Math.floor(Math.random() * 5) + 1);
this.createFood(this.size)
this.setRect(this.size)
}
// 創(chuàng)建食物
createFood(size){
this.div = document.createElement('div');
this.div.setAttribute('class', 'food_body_' + size);
document.body.appendChild(this.div);
}
// 位置設(shè)定
setRect(size){
let screenWidth = document.body.offsetWidth
let screenHeight = document.body.offsetHeight
let width = size * 5
let height = size * 5
let x = Math.floor(Math.random() * (screenWidth - 4 * width)) + width
let y = Math.floor(Math.random() * (screenHeight - 4 * height)) + height
this.rect = new Rect({ x, y, width, height })
this.div.style.top = this.rect.y + 'px'
this.div.style.left = this.rect.x + 'px'
this.div.style.width = this.rect.width + 'px'
this.div.style.height = this.rect.height + 'px'
}
// 銷毀
destroy(){
this.div.remove()
}
}
class Snake {
bodys = []
foods = []
ballOption = null
angle = null
isPause = false
constructor(){
}
// 開始
begin(){
this.clearAll()
this.createHeader()
this.foods = Food.scatterFoods()
this.createBall()
return this
}
// 清除全部元素、再次繪制
clearAll(){
this.angle = null
this.ballOption = null
this.bodys = []
this.foods = []
this.isPause = false
document.body.innerHTML = `
<div id="optionDiv"></div>
<div id="resultWarningDiv" class="hideResultWarningDiv">
<div class="warningContent">
<span id="warningContentDiv">22</span>
<span id="resumeGameDiv"> 繼續(xù) </span>
</div>
</div>
`
}
// 創(chuàng)建搖桿
createBall(){
this.ballOption = new BallOption('optionDiv',(angle)=>{
angle = 270 + angle
if(angle > 360){
angle = angle - 360
}
this.angle = angle * (Math.PI / 180)
})
this.ballOption.createOptionView()
}
// 創(chuàng)建蛇頭
createHeader(){
let x = document.body.offsetWidth / 2.0
let y = document.body.offsetHeight / 2.0
let header = new Body(new Rect({ x, y }))
this.bodys.push(header)
}
// 吃
eat(foodItem){
let lastBody = this.getTailer()
let body = new Body(lastBody)
this.bodys.push(body)
// 移除食物
foodItem.destroy()
}
// 移動
move(){
requestAnimationFrame(() => {
if(!this.isPause){
this.setNewHeaderDirection()
this.collisionDetection()
this.checkIsSucceeded()
}
this.move()
});
}
// 轉(zhuǎn)向
setNewHeaderDirection(){
for(let i = this.bodys.length - 1; i >= 0;i--){
let item = this.bodys[i]
if(i == 0){
item.setRect(this.nextStepRect())
} else {
item.setRect(this.bodys[i - 1].rect)
}
}
}
// 獲取蛇頭
getHeader(){
return this.bodys[0]
}
// 獲取蛇尾
getTailer(){
return this.bodys[this.bodys.length - 1]
}
// 蛇頭下一步的位置計(jì)算
nextStepRect(){
let header = this.getHeader()
let step = 1.5
let addX = 0
let addY = 0
addX = this.angle ? step * Math.cos(this.angle) : step
addY = this.angle ? step * Math.sin(this.angle) : 0
let x = header.rect.x + addX
let y = header.rect.y + addY
return new Rect({x, y })
}
// 吃到食物檢測
collisionDetection(){
let headerRect = this.getHeader().rect
this.foods = this.foods.filter((foodItem)=>{
let foodRect = foodItem.rect
let isDetection = this.checkRectOverlap(headerRect,foodRect)
if(isDetection){
//根據(jù)size大小成長
for(let i = 0; i < foodItem.size; i ++){
this.eat(foodItem)
}
}
return !isDetection
})
}
// 蛇頭與食物區(qū)域是否重疊判斷
checkRectOverlap(rect1, rect2) {
// 提取矩形的坐標(biāo)和尺寸
let x1 = rect1.x;
let y1 = rect1.y;
let width1 = rect1.width;
let height1 = rect1.height;
let x2 = rect2.x;
let y2 = rect2.y;
let width2 = rect2.width;
let height2 = rect2.height;
// 檢查是否有重疊
return (x1 < x2 + width2 &&
x1 + width1 > x2 &&
y1 < y2 + height2 &&
y1 + height1 > y2)
}
// 游戲結(jié)果計(jì)算
checkIsSucceeded(){
let screenWidth = document.body.offsetWidth
let screenHeight = document.body.offsetHeight
let { x, y } = this.getHeader().rect
if(x >= screenWidth || x <= 0 || y >= screenHeight || y <= 0){
this.isPause = true
this.resultWarning('游戲結(jié)束')
}
if(this.foods.length == 0){
this.isPause = true
this.resultWarning('厲害!')
}
}
// 結(jié)果提醒
resultWarning(content = '游戲結(jié)束'){
document.getElementById('resultWarningDiv').classList.remove('hideResultWarningDiv')
document.getElementById('warningContentDiv').innerText = content
document.getElementById('resumeGameDiv').onclick = ()=>{
document.body.innerHTML = ''
this.begin()
}
}
}
class EleOption{
//添加操作dom ID
eleId
constructor(eleId){
this.eleId = eleId
}
//獲取當(dāng)前關(guān)聯(lián)的el
getCurrentEle(){
return document.getElementById(this.eleId)
}
//獲取el寬度
getEleWidth(el){
return el.offsetWidth
}
//獲取el高度
getEleHeight(el){
return el.offsetHeight
}
//設(shè)置背景顏色
setBackgroundColor(el,color){
el.style.backgroundColor = color
}
//設(shè)置寬度
setWidth(el,w){
el.style.width = w + 'px'
}
//設(shè)置高度
setHeight(el,h){
el.style.height = h + 'px'
}
//設(shè)置圓角
setCircle(el){
el.style.borderRadius = (this.getEleWidth(el) / 2.0 )+ 'px'
}
//設(shè)置絕對定位
setAbsolutePosition(el){
el.style.position = 'absolute'
}
//設(shè)置透明度
setTransparency(el,alpha){
el.style.opacity = alpha / 100
}
//設(shè)置為父el中心位置
setSupCenter(el){
if(el.style.position != 'absolute'){
this.setAbsolutePosition(el)
let superElWidth = this.getEleWidth(this.getSuperEl(el))
let superElHeight = this.getEleHeight(this.getSuperEl(el))
let width = this.getEleWidth(el)
let height = this.getEleHeight(el)
el.style.left = ((superElWidth - width) / 2.0) + 'px'
el.style.top = ((superElHeight - height) / 2.0) + 'px'
}
}
//設(shè)置中心位置
setCenter(el,point){
if(el.style.position != 'absolute'){
this.setAbsolutePosition(el)
}
el.style.left = point.x + 'px'
el.style.top = point.y + 'px'
}
//獲取父類el
getSuperEl(el){
return el.parentNode
}
//獲取el
getElById(elId){
return document.getElementById(elId)
}
//創(chuàng)建el
createEl(elId){
let el = document.createElement('div')
if(elId){
el.setAttribute('id',elId)
}
return el
}
//添加子el
addSubEl(superEl,subEl){
superEl.appendChild(subEl);
}
//取消交互
cancleUserInreface(el){
el.style.pointerEvents = 'none'
}
//添加move事件
addMoveEvent(el,ballOption,mcb,emcb){
el.onmousemove = (event)=>{
mcb(this.getMoveEventPoint(event,el),ballOption)
}
el.onmouseout = (_)=>{
emcb(ballOption)
}
}
//move事件監(jiān)聽
getMoveEventPoint(event,el){
let x = event.clientX - this.getSuperEl(el).offsetLeft
let y = event.clientY - this.getSuperEl(el).offsetTop
return {x,y}
}
//獲取中心點(diǎn)
getCenterPoint(off){
let x = this.getSuperEl(this.getCurrentEle()).offsetLeft + (this.getEleWidth(this.getCurrentEle()) / 2.0) - off.offX
let y = this.getSuperEl(this.getCurrentEle()).offsetTop + (this.getEleHeight(this.getCurrentEle()) / 2.0) - off.offY
return {x,y}
}
}
class BallOption{
//添加操作dom ID
eleId
//el操作對象
eleOption
//控制球?qū)ο?/span>
ball
//控制球尺寸
ballWidth
ballHeight
ballOffX
ballOffY
//是否觸碰過控制球
isTouchedBall = false
//控制區(qū)域
optionRangeView
optionRangeViewCenterPoint
//上一次角度
lastDeg
//角度回調(diào)
angleCallBack
constructor(eleId,angleCallBack){
this.eleId = eleId
this.angleCallBack = angleCallBack
this.eleOption = new EleOption(eleId)
}
//創(chuàng)建操作框
createOptionView(){
if(this.eleId != undefined){
this.createOptionRangeView()
this.createOptionBallView()
}
}
//繪制操作范圍
createOptionRangeView(){
let width = this.eleOption.getEleWidth(this.eleOption.getCurrentEle())
let height = this.eleOption.getEleHeight(this.eleOption.getCurrentEle())
this.optionRangeView = this.eleOption.createEl('optionRangeViewEl')
this.eleOption.addSubEl(this.eleOption.getCurrentEle(),this.optionRangeView)
this.eleOption.setBackgroundColor(this.optionRangeView,'rgb(248,248,248)')
this.eleOption.setWidth(this.optionRangeView,width)
this.eleOption.setHeight(this.optionRangeView,height)
this.eleOption.setCircle(this.optionRangeView)
//添加拖拽事件
this.eleOption.addMoveEvent(optionRangeViewEl,this,this.makeBallFollowScroll,this.resetBall)
}
//控制球隨鼠標(biāo)滾
makeBallFollowScroll(point,ballOption){
let x = (point.x - ballOption.ballOffX)
let y = (point.y - ballOption.ballOffY)
let currentPoint = {x,y}
if(ballOption.checkIsTouchControlBall(point)){
ballOption.eleOption.setCenter(ballOption.ball,currentPoint)
ballOption.getCurrentAngle(point)
}
}
//檢測是否碰觸過控制球
checkIsTouchControlBall(point){
if(!this.isTouchedBall){
let isTouchBall = (
point.x > this.optionRangeViewCenterPoint.x - this.ballWidth &&
point.x < this.optionRangeViewCenterPoint.x + this.ballWidth &&
point.y > this.optionRangeViewCenterPoint.y - this.ballHeight &&
point.y < this.optionRangeViewCenterPoint.y + this.ballHeight
)
if(isTouchBall){
this.isTouchedBall = true
this.eleOption.setTransparency(this.ball,100)
}
}
return this.isTouchedBall
}
//鼠標(biāo)移出事件
resetBall(ballOption){
ballOption.isTouchedBall = false
ballOption.eleOption.setCenter(ballOption.ball,ballOption.optionRangeViewCenterPoint)
ballOption.eleOption.setTransparency(ballOption.ball,40)
if(ballOption.angleCallBack){
ballOption.lastDeg = 0
ballOption.angleCallBack(ballOption.lastDeg)
}
}
//計(jì)算角度
getCurrentAngle(point){
let addX = (point.x - this.eleOption.getEleWidth(this.optionRangeView) / 2.0)
let addY = (point.y - this.eleOption.getEleHeight(this.optionRangeView) / 2.0)
if(addY != 0){
let tan = addX / addY
let angle = Math.atan(tan)
this.lastDeg = (angle / Math.PI) * 180
if(addX <= 0 && addY < 0){
this.lastDeg = this.lastDeg
} else if(addX <= 0 && addY > 0){
this.lastDeg = (180 - Math.abs(this.lastDeg))
} else if(addX >= 0 && addY > 0){
this.lastDeg = 180 + Math.abs(this.lastDeg)
} else if(addX >= 0 && addY < 0){
this.lastDeg = (360 - Math.abs(this.lastDeg))
}
}
if(this.angleCallBack){
this.angleCallBack(360 - this.lastDeg)
}
}
//繪制球滾動
createOptionBallView(){
let scale = 3.2
this.ballWidth = this.eleOption.getEleWidth(this.eleOption.getCurrentEle()) / scale
this.ballHeight = this.eleOption.getEleHeight(this.eleOption.getCurrentEle()) / scale
this.ballOffX = this.ballWidth / 2.0
this.ballOffY = this.ballHeight / 2.0
this.ball = this.eleOption.createEl('optionBallViewEl')
this.eleOption.addSubEl(this.eleOption.getCurrentEle(),this.ball)
this.eleOption.setBackgroundColor(this.ball,'black')
this.eleOption.setWidth(this.ball,this.ballWidth)
this.eleOption.setHeight(this.ball,this.ballHeight)
this.eleOption.setCircle(this.ball)
this.eleOption.setSupCenter(this.ball)
this.eleOption.cancleUserInreface(this.ball)
this.eleOption.setTransparency(this.ball,40)
//保存中心點(diǎn)坐標(biāo)
this.optionRangeViewCenterPoint = this.eleOption.getCenterPoint({offX:this.ballOffX,offY:this.ballOffY})
}
}
let snake = new Snake()
snake.begin().move()