【漢諾塔】小游戲開發(fā)教程

游戲簡介

漢諾塔是源于印度一個古老傳說的益智游戲将鸵,傳說大梵天創(chuàng)造世界的時候順便搞了三根柱子,一根柱子上摞著一堆從大到小的圓環(huán)佑颇,他命令婆羅門把圓環(huán)全部移動到另一個柱子上顶掉,依舊是從大到小,且移動規(guī)則如下:

1.一次只能把一個圓環(huán)從一根柱子移動到另一根柱子上

2.圓環(huán)的上面不能放比它大的圓環(huán)

詳細(xì)介紹及解法請參考文章:漢諾塔與遞歸挑胸。

最終的成果示例請點擊:漢諾塔小游戲痒筒。

溫馨提示:本篇教程屬于從頭到尾面面俱到型,雖然開發(fā)上本身是沒什么難度的茬贵,但不妨礙把它做成一個很完善的游戲簿透,所以它很長。

布局

本項目使用vue作為基礎(chǔ)框架解藻。

使用這些視圖框架的主要思想就是操作數(shù)據(jù)老充,視圖更新交給框架,只要做好數(shù)據(jù)和視圖的映射即可螟左,所以本游戲的核心也就是維護(hù)一些數(shù)據(jù)及操作數(shù)據(jù)啡浊。

首先要做的是布局,要模擬出上圖中的三根柱子及圓環(huán)胶背。本游戲全部使用DOM來布局虫啥,不使用canvas。

柱子的布局很簡單奄妨,用div元素來作為線段涂籽,代碼如下:

<template>
    <div class="container">
        <div class="(column, cIndex)" v-for="item in columnList" :key="item.name">
            <div class="col"></div>
            <div class="land"></div>
            <div class="name">{{item.name}}</div>
        </div>
    </div>
</template>

<script>
export default {
    name: 'Game',
    data() {
        return {
            columnList: [
                {
                    name: '起始柱'
                },
                {
                    name: '中轉(zhuǎn)柱'
                },
                {
                    name: '終點柱'
                }
            ]
        }
    }
}
</script>

樣式部分很簡單就不列出來了,效果如下:

接下來是圓環(huán)砸抛,因為有三根柱子评雌,所以使用三個數(shù)組來存放树枫,每個圓環(huán)用一個對象來表示,每個圓環(huán)有顏色景东、代表大小的序號屬性砂轻,序號從1開始,1代表最大斤吐,因為圓環(huán)數(shù)量可變搔涝,所以每個圓環(huán)的寬高、位置都需要動態(tài)進(jìn)行計算和措,渲染同樣是循環(huán)進(jìn)行渲染庄呈,三個圓環(huán)的情況如下所示:

<template>
<div class="container">
    <div class="column" v-for="(item, cIndex) in columnList" :key="item.name">
        <!--省略...-->
        <div class="ringsBox">
            <div 
                class="ring" 
                v-for="(ringItem, index) in ringList[item.prop]" 
                :key="ringItem.order" 
                :style="{
                    width: (wsize - (ringItem.order - 1) * 10) + '%',
                    height: hsize / ringNum + '%',
                    backgroundColor: ringItem.color,
                    left: (100 - (wsize - (ringItem.order - 1) * 10)) / 2 + '%',
                    bottom: (hsize / ringNum) * index + '%'
                }"
            ></div>
        </div>
    </div>
</div>
</template>

<script>
export default {
    name: "Game",
    data() {
        return {
            // 柱子
            // 增加了一個prop屬性,代表該柱子對應(yīng)的圓環(huán)數(shù)組
            columnList: [
                {
                    name: "起始柱",
                    prop: "startColRingList",
                },
                {
                    name: "中轉(zhuǎn)柱",
                    prop: "transferColRingList",
                },
                {
                    name: "終點柱",
                    prop: "endColRingList",
                },
            ],
            // 圓環(huán)
            // 圓環(huán)數(shù)量
            ringNum: 3,
            // 圓環(huán)數(shù)據(jù)
            ringList: {
                startColRingList: [
                    {
                        color: "#ffa36c",
                        order: 1,
                    },
                    {
                        color: "#00bcd4",
                        order: 2,
                    },
                    {
                        color: "#848ccf",
                        order: 3,
                    }
                ],
                transferColRingList: [],
                endColRingList: [],
            },
        };
    },
    computed: {
        // 最大寬度值
        wsize() {
            return this.ringNum <= 5 ? 50 :  this.ringNum * 10
        },
        // 最大高度值
        hsize() {
            return this.ringNum <= 3 ? 30 :  this.ringNum * 10
        }
    }
};
</script>

效果如下所示:

拖動

這個游戲主要的交互就是拖動圓環(huán)到另一根柱子上派阱,所以圓環(huán)需要支持拖動诬留,需要注意的是每根柱子上都只有最上面的一個圓環(huán)能被拖動,且拖動到的柱子上存在的最上面的圓環(huán)還要比它大贫母,否則不允許落下文兑。

具體的實現(xiàn)就是監(jiān)聽鼠標(biāo)按下事件、鼠標(biāo)移動事件腺劣、鼠標(biāo)松開事件绿贞,鼠標(biāo)按下移動時改變該圓環(huán)的transform: translate(x,y)屬性來進(jìn)行移動,鼠標(biāo)松開時判斷當(dāng)前圓環(huán)被拖動到的位置是否在三個圓環(huán)的某一個區(qū)域內(nèi)橘原,是的話再判斷圓環(huán)能否落到該柱子上籍铁,符合條件就把該圓環(huán)的數(shù)據(jù)從之前柱子的數(shù)組移到落下柱子的數(shù)組內(nèi),否則就復(fù)位transform屬性讓圓環(huán)回去靠柑。

綁定事件需要注意的是按下事件綁定到圓環(huán)上寨辩,而移動和松開事件要綁定到body上吓懈,否則當(dāng)你移動過快時鼠標(biāo)指針可能會和圓環(huán)不同步而超出圓環(huán)歼冰,進(jìn)而當(dāng)你松開后就監(jiān)聽不到松開事件了。

<template>
<div class="container">
    <div class="column" v-for="(column, cIndex) in columnList" :key="item.name">
        <!--省略...-->
        <div class="ringsBox">
            <div 
                class="ring" 
                v-for="(ringItem, index) in ringList[item.prop]" 
                <!--省略...-->
                @mousedown="mousedown($event, ringItem, index, item.prop, cIndex)"
            ></div>
        </div>
    </div>
</div>
</template>

<script>
export default {
    name: "Game",
    // ...
    mounted() {
        this.bindEvent()
    },
    beforeDestroy() {
        this.unbindEvent()
    },
    methods: {
        // 鼠標(biāo)移動事件和松開事件綁定到body上
        bindEvent() {
            document.body.addEventListener('mousemove', this.mousemove)
            document.body.addEventListener('mouseup', this.mouseup)
        },

        // 解綁事件
        unbindEvent() {
            document.body.removeEventListener('mousemove', this.mousemove)
            document.body.removeEventListener('mouseup', this.mouseup)
        }
    }
};
</script>

接下來重點實現(xiàn)這三個事件處理函數(shù)耻警。

先定義一些必要的變量:

{
    // 拖動變量
    dragProp: '',// 當(dāng)前拖動圓環(huán)所屬的柱子
    dragOrder: 0,// 當(dāng)前拖動圓環(huán)的大小序號
    dragIndex: -1,// 當(dāng)前拖動圓環(huán)在原柱子上的索引
    dragColumnIndex: -1,// 當(dāng)前拖動圓環(huán)所在柱子的索引
    draging: false,// 當(dāng)前是否是拖動中
    startPos: {// 鼠標(biāo)按下時的坐標(biāo)
        x: 0,
        y: 0
    },
    dragPos: {// 鼠標(biāo)移動的偏移量
        x: 0,
        y: 0
    }
}

拖動是拖動當(dāng)前鼠標(biāo)按下的圓環(huán)隔嫡,因為是在循環(huán)體里添加的css屬性,所以對所有圓環(huán)都是有效的甘穿,那么怎么判斷目標(biāo)圓環(huán)是哪個圓環(huán)腮恩,對于圓環(huán)來說,它的order屬性是唯一的温兼,所以根據(jù)dragOrder變量就可以定位到了秸滴,是的話就讓它的translate的值隨著dragPos的值進(jìn)行變化:

<template>
<div class="container">
    <div class="column" v-for="(column, cIndex) in columnList" :key="item.name">
        <!--省略...-->
        <div class="ringsBox">
            <div 
                class="ring" 
                v-for="(ringItem, index) in ringList[item.prop]" 
                :key="ringItem.order" 
                :style="{
                    <!--省略...-->
                    transform: dragOrder === ringItem.order ? `translate(${dragPos.x}px, ${dragPos.y}px)` : 'translate(0px, 0px)'
                }"
            ></div>
        </div>
    </div>
</div>
</template>

鼠標(biāo)按下事件處理函數(shù)的主要邏輯是設(shè)置拖動標(biāo)志位、緩存當(dāng)前拖動的一些數(shù)據(jù)募判,比如當(dāng)前拖動圓環(huán)的相關(guān)信息及鼠標(biāo)按下的位置信息:

{
    // 鼠標(biāo)按下事件
    mousedown(e, ringItem, index, prop, columnIndex) {
        // 當(dāng)按下的不是該柱子最上面的圓環(huán)時不做任何處理
        if (index < this.ringList[prop].length - 1) {
            return
        }
        this.dragProp = prop
        this.dragOrder = ringItem.order
        this.dragIndex = index
        this.dragColumnIndex = columnIndex
        this.startPos.x = e.clientX
        this.startPos.y = e.clientY
        this.draging = true
    }
}

鼠標(biāo)移動事件處理函數(shù)的功能是實時更新拖動的偏移量荡含,圓環(huán)就會跟著動了:

{
    // 鼠標(biāo)移動事件
    mousemove(e) {
        // 不是拖動的情況直接返回
        if (!this.draging) {
            return
        }
        this.dragPos.x = e.clientX - this.startPos.x
        this.dragPos.y = e.clientY - this.startPos.y
    }
}

鼠標(biāo)松開事件是最重要的咒唆,在該函數(shù)里需要判斷圓環(huán)是否拖動到某個柱子區(qū)域內(nèi)及能否落下及具體的落下操作:

{
    // 鼠標(biāo)松開事件
    mouseup() {
        // 不是拖動的情況直接返回
        if (!this.draging) {
            return
        }
        // 復(fù)位拖動標(biāo)志位
        this.draging = false
        // 計算圓環(huán)拖動到哪個柱子上
        let columnIndex = this.checkInColumnIndex(this.dragOrder)
        // 判斷圓環(huán)是否可以落到該柱子上
        let canDraged = this.canDraged(columnIndex, this.dragOrder)
        // 能落下的話就移動該圓環(huán)的數(shù)據(jù)
        if (canDraged) {
            this.dragToColumn(columnIndex, this.dragProp, this.dragIndex)
        }
        // 復(fù)位
        this.reset()
    }
}

接下來一步步來實現(xiàn)該函數(shù)里的幾個方法。

因為涉及到位置計算释液,所以需要獲取實際的DOM元素全释,先在模板里加上ref用于引用DOM:

<template>
<div class="container">
    <div class="column" v-for="(item, cIndex) in columnList" :key="item.name" :ref="'column' + cIndex">
        <div class="ringsBox">
            <div 
                class="ring" 
                v-for="(ringItem, index) in ringList[item.prop]" 
                :ref="'ring' + ringItem.order"
            ></div>
        </div>
    </div>
</div>
</template>

首先柱子區(qū)域是一個矩形,如下所示:

然后圓環(huán)其實也是一個矩形误债,那么問題實際上就轉(zhuǎn)換為求兩個矩形是否相交浸船,這個是很簡單的,方便起見寝蹈,把它們的位置都相對于瀏覽器窗口左上角來計算李命,那么滿足下面的條件圓環(huán)和柱子區(qū)域即相交:

1.圓環(huán)的右側(cè)距窗口左側(cè)的距離大于柱子區(qū)域左側(cè)距窗口左側(cè)的距離、同時圓環(huán)左側(cè)距窗口的距離小于柱子區(qū)域右側(cè)距窗口左側(cè)的距離
2.圓環(huán)的頂部距窗口頂部的距離小于柱子區(qū)域的底部距窗口頂部的距離躺盛、同時圓環(huán)的底部距窗口頂部的距離大于柱子區(qū)域頂部距窗口頂部的距離

翻譯成代碼如下:

{
    // 檢查某個圓環(huán)的位置是否在某個柱子區(qū)域內(nèi)
    checkInColumnIndex(order) {
        let result = -1
        // 獲取圓環(huán)相當(dāng)于瀏覽器窗口的位置信息
        let ringRect = this.$refs['ring' + order][0].getBoundingClientRect()
        // 遍歷獲取柱子區(qū)域相當(dāng)于瀏覽器窗口的位置信息
        ;[0, 1, 2].forEach((index) => {
            // 獲取區(qū)域位置信息
            let {left, right, top, bottom} = this.$refs['column' + index][0].getBoundingClientRect()
            // 重合檢查
            if (
                (ringRect.right >= left && ringRect.left <= right) && (ringRect.top <= bottom && ringRect.bottom >= top)) 
            {
                result = index
            }
        })
        return result
    }
}

知道了在哪個圓環(huán)后接下來要判斷是否可以落下项戴,根據(jù)游戲規(guī)則,小的圓環(huán)上不能放大的槽惫,所以判斷當(dāng)前柱子上最小的圓環(huán)是否比當(dāng)前圓環(huán)大即可:

{
    // 判斷某個圓環(huán)是否可以落到指定索引的柱子上
    canDraged(columnIndex, order) {
        // 不在圓環(huán)區(qū)域內(nèi)直接返回
        if (columnIndex === -1) {
            return 
        }
        let prop = this.columnList[columnIndex].prop
        let list = this.ringList[prop]
        // 柱子為空則可以落下
        if (list.length <= 0) {
            return true
        }
        // 數(shù)組里最后一項即是當(dāng)前柱子最小的圓環(huán)
        let minOrder = list[list.length - 1].order
        if (order > minOrder) {
            return true
        }
        return false
    }
}

判斷如果是可以落下的那么直接將該圓環(huán)的數(shù)組從原柱子數(shù)組移到目標(biāo)數(shù)組即可:

{
    // 某個圓環(huán)落到指定索引的柱子上
    dragToColumn(columnIndex, prop, index) {
        // 從原數(shù)組取出
        let ring = this.ringList[prop].splice(index, 1)[0]
        // 追加到目標(biāo)數(shù)組
        let toProp = this.columnList[columnIndex].prop
        this.ringList[toProp].push(ring)
    }
}

如果不能落下的話那么就讓圓環(huán)回去周叮,圓環(huán)的位置要回去的話直接把dragPos的值恢復(fù)要0即可,其他的相關(guān)變量也需要復(fù)位:

{
    // 拖動完成后復(fù)位
    reset() {
        this.dragProp = ''
        this.dragOrder = 0
        this.dragIndex = null
        this.draging = false
        this.dragColumnIndex = -1
        this.startPos.x = 0
        this.startPos.x = 0
        this.dragPos.x = 0
        this.dragPos.y = 0
    }
}

到這里游戲的核心功能就完成了界斜,已經(jīng)可以玩了:

圖上的圓環(huán)移到某個區(qū)域內(nèi)顯示的背景突出效果實現(xiàn)也很簡單仿耽,在移動過程中不斷檢測是否相交,是的話就給對應(yīng)的區(qū)域加上背景的類名:

<template>
<div class="container">
    <div class="column" v-for="(item, cIndex) in columnList" :key="item.name" :ref="'column' + cIndex" :class="{dragIn: dragingColumnIndex === cIndex}">
        
    </div>
</div>
</template>

{
    data() {
        return {
            dragingColumnIndex: -1//拖動過程中實時相交的區(qū)域索引
        }
    },
    methods: {
        mousemove(e) {
            //...
            this.dragingColumnIndex = this.checkInColumnIndex(this.dragOrder)
        }
    }
}

完成檢測

每一次拖動后都要判斷游戲是否完成各薇,判斷方式很簡單项贺,檢測目標(biāo)數(shù)組不為空,而其他兩根柱子的數(shù)組為空就可以了峭判,或者直接檢測目標(biāo)數(shù)組里的圓環(huán)數(shù)量是否和當(dāng)前層數(shù)對應(yīng)开缎,反正方式有很多。

{
    // 檢測游戲是否完成
    checkPass() {
        if (this.ringList.endColRingList.length === this.ringNum) {
            alert('恭喜你林螃,完成啦')
        }
    }
}

就是這么簡單奕删。

游戲基本功能到這里就結(jié)束了,但是作為一個有夢想有追求的人疗认,完成基本功能只意味著開始完残,隨便想想煞聪,就能想到還有很多能做的:游戲?qū)訑?shù)選擇妖滔、操作按鈕、信息顯示俯邓,還有一些高級功能:回退操作缎浇、自動操作扎拣、步驟回放等等,因為篇幅原因,本篇不會全部展開講解二蓝,只挑一兩個來淺析一下尊蚁,不要走開,精彩繼續(xù)侣夷。

動畫過度

首先先做個優(yōu)化,目前來說百拓,當(dāng)你拖動圓環(huán)到某個柱子上松開時圓環(huán)是瞬間顯示到柱子上的,而不是過渡過去的决帖,包括當(dāng)松開鼠標(biāo)不符合落下條件圓環(huán)回去也是一樣,突變總是不優(yōu)雅的蓖捶,我們讓它平滑的滑動起來地回。

因為圓環(huán)是使用css的translate屬性來跟隨鼠標(biāo)動的俊鱼,所以只要給它加上transition屬性即可平滑過渡,要注意的是拖動過程中該屬性的值必須為none并闲,否則你每拖動一下细睡,它都要緩一下過渡過去,所以該屬性的值要動態(tài)進(jìn)行設(shè)置帝火。

圓環(huán)不符合落下條件時復(fù)位的過渡不需要修改,加上transition就有過渡能力了蠢壹,主要是符合落下條件時從鼠標(biāo)松開的位置過渡到目標(biāo)位置需要計算一下九巡,看圖:

因為拖動中的圓環(huán)的transition的坐標(biāo)也就是dragPos屬性的值是相當(dāng)于鼠標(biāo)按下的位置來說的,其實也就是圓環(huán)開始的位置求妹,所以只要知道圓環(huán)即將落到的目標(biāo)位置相對于圓環(huán)開始的位置佳窑,把該坐標(biāo)設(shè)置給dragPos就可以了父能,css動畫方式就是如此的簡單明了:

<template>
<div class="container">
    <div class="column" v-for="(item, cIndex) in columnList">
        <div class="ringsBox">
            <div 
                class="ring" 
                v-for="(ringItem, index) in ringList[item.prop]" 
                :style="{
                    <!--省略...-->
                    transition: transition
                }"
            ></div>
        </div>
    </div>
</div>
</template>

{
    data() {
        return {
            transition: 'none'
        }
    },
    methods: {
        mousedown(e, ringItem, index, prop, columnIndex) {
            // ...
            // 鼠標(biāo)按下時說明可能要進(jìn)行拖動,那么該屬性要設(shè)為null
            this.transition = 'none'
            // ...
        },
        // 重點改造鼠標(biāo)松開事件函數(shù)
        async mouseup() {
            if (!this.draging) {
                return
            }
            this.draging = false
            let columnIndex = this.checkInColumnIndex(this.dragOrder)
            let canDraged = this.canDraged(columnIndex, this.dragOrder)
            // 設(shè)置過渡效果
            this.transition = 'all 0.5s'
            if (canDraged) {
                // 核心函數(shù)溉委,讓圓環(huán)從松開的位置移動到目標(biāo)位置,因為過渡需要時間坡慌,所以使用await進(jìn)行等待
                await this.moveToNewPos(columnIndex, this.dragProp, this.dragIndex)
                // 圓環(huán)物理位置過去以后藻三,實際該圓環(huán)的數(shù)據(jù)還是在原來的柱子數(shù)組里的,所以還是需要把它移到目標(biāo)數(shù)組
                this.dragToColumn(columnIndex, this.dragProp, this.dragIndex)
                // 過渡完以后刪掉過渡效果
                this.transition = 'none'
                // 復(fù)位數(shù)據(jù)
                this.reset()
                this.checkPass()
            } else {
                this.reset()
            }
        }
    }
}

接下來就是要實現(xiàn)上面的移動函數(shù)moveToNewPos熄求,其實就是計算目標(biāo)位置的坐標(biāo)逗概,該坐標(biāo)是相當(dāng)于圓環(huán)起始坐標(biāo)來說的弟晚,方便計算也先它們都轉(zhuǎn)化為相當(dāng)于瀏覽器窗口,然后相減就得到了最終結(jié)果:

{
   moveToNewPos(columnIndex, prop, index) {
        // 因為過渡需要500毫秒逾苫,所以使用promise
        return new Promise((resolve, rejct) => {
            let ring = this.ringList[prop][index]
            // 將圓環(huán)起始坐標(biāo)轉(zhuǎn)化為距瀏覽器窗口坐標(biāo)
            let startPos = this.getRingPosOffsetWindow(this.dragColumnIndex, ring.order, true)
            // 將圓環(huán)目標(biāo)坐標(biāo)轉(zhuǎn)化為距瀏覽器窗口坐標(biāo)
            let endPos = this.getRingPosOffsetWindow(columnIndex, ring.order)
            // 相減得到目標(biāo)坐標(biāo)相當(dāng)于起始坐標(biāo)的值
            this.dragPos.x = endPos.left - startPos.left
            this.dragPos.y = endPos.top - startPos.top
            // 讓圓環(huán)過渡完
            setTimeout(() => {
                resolve()
            }, 500);
        })
    } 
}

getRingPosOffsetWindow方法是計算某個柱子上指定索引的圓環(huán)的位置相當(dāng)于瀏覽器窗口的距離,第三個參數(shù)為true代表該圓環(huán)是否已經(jīng)存在于該柱子铅搓,為false代表是即將落下的目標(biāo)位置:

{
    getRingPosOffsetWindow(columnIndex, order, exist) {
        // 該柱子的圓環(huán)數(shù)組
        let prop = this.columnList[columnIndex].prop
        // 該柱子區(qū)域的尺寸位置信息
        let rect = this.$refs['column' + columnIndex][0].getBoundingClientRect()
        // 圓環(huán)在該柱子上的索引
        let index = this.ringList[prop].length - (exist ? 1 : 0)
        // 圓環(huán)相當(dāng)于柱子區(qū)域的位置信息
        let left = (100 - (this.wsize - (order - 1) * 10)) / 2 + '%'
        let bottom = (this.hsize / this.ringNum) * index + '%'
        let height = this.hsize / this.ringNum + '%'
        // 轉(zhuǎn)換為像素
        let leftPx = rect.width * parseFloat(left) / 100
        // 底部線段占了5像素
        let _height = rect.height - 5
        let topPx = _height - (_height * parseFloat(bottom) / 100) - (parseFloat(height) * _height / 100)
        // 轉(zhuǎn)換為屏幕上的坐標(biāo)
        let windowLeftPx = rect.left + leftPx
        let windowTopPx = rect.top + topPx
        return {
            left: windowLeftPx, 
            top: windowTopPx
        }
    }
}

到這里松開圓環(huán)圓環(huán)就會過渡到目標(biāo)位置狸吞,

最少步數(shù)與自動操作

漢諾塔游戲可以用遞歸來求解,詳細(xì)了解可參考文章開頭提到的文章便斥,此處不再贅述威始,直接貼出遞歸函數(shù):

export default {
    data() {
        return {
            minStepNum: 0//當(dāng)前層數(shù)最少步數(shù)
        }  
    },
    methods: {
        // 計算指定層數(shù)的解法吉最少步數(shù)
        resolveHannuota(num, start, transfer, end) {
            if (num <= 0) {
                return;
            }
            this.resolveHannuota(num - 1, start, end, transfer)
            console.log(start + '->' + end)
            this.minStepNum++
            this.resolveHannuota(num - 1, transfer, start, end)
        }
    }
}

層數(shù)改變很簡單黎棠,把之前寫死的startColRingList數(shù)組改成遍歷生成就可以了,每次層數(shù)改變后都調(diào)一下上面的resolveHannuota方法脓斩,minStepNum累加的結(jié)果就是最少次數(shù)随静,console.log打印的就是步驟吗讶,三層打印的結(jié)果如下所示:

startColRingList->endColRingList
startColRingList->transferColRingList
endColRingList->transferColRingList
startColRingList->endColRingList
transferColRingList->startColRingList
transferColRingList->endColRingList
startColRingList->endColRingList

可以通過解析該數(shù)據(jù)來實現(xiàn)自動操作恋捆。

// 柱子索引
const propIndex = {
    startColRingList: 0,
    transferColRingList: 1,
    endColRingList: 2,
}
// 自動操作
function auto() {
    let index = 0
    let loop = async () => {
        // autoStepList數(shù)組就是上面console打印的內(nèi)容
        if (index > this.autoStepList.length - 1) {
            return;
        }
        let cur = this.autoStepList[index]
        let columnIndex = propIndex[cur.to]
        this.dragColumnIndex = propIndex[cur.from]
        let dragIndex = this.ringList[cur.from].length - 1
        this.transition = "all 0.5s";
        this.dragOrder = this.ringList[cur.from][dragIndex].order
        // 調(diào)用之前過渡的方法
        await this.moveToNewPos(columnIndex, cur.from, dragIndex);
        // 移動數(shù)組元素
        this.dragToColumn(columnIndex, cur.from, dragIndex);
        this.transition = "none";
        this.dragPos.x = 0
        this.dragPos.y = 0
        index++
        setTimeout(() => {
            loop()
        }, 500);
    }
    loop()
}

返回上一步

返回上一步也很簡單沸停,通過數(shù)組記錄下每一步,然后每點一次就把數(shù)組最后一項彈出來爽茴,通過上述動畫方式移動對應(yīng)的圓環(huán)即可绰垂。

首先在之前的mouseup函數(shù)里保存每一步的操作:

{
    // 鼠標(biāo)松開事件函數(shù)
    async mouseup() {
        // ...
        this.transition = 'all 0.5s'
        if (canDraged) {
            await this.moveToNewPos(columnIndex, this.dragProp, this.dragIndex)
            
            // 在這里把這一步的操作添加到數(shù)組里,注意回退操作是把這一步的目標(biāo)位置回到開始位置
            this.historyList.push({
                to: this.dragProp,
                from: this.columnList[columnIndex].prop
            })
            
            // ...
        } else {
            this.reset()
        }
    }
}

然后點點擊回退按鈕時彈出最后一步進(jìn)行回退:

{
    // 返回上一步
    async goback() {
        if (this.historyList.length <= 0) {
            return
        }
        let cur = this.historyList.pop()
        let columnIndex = propIndex[cur.to]
        this.dragColumnIndex = propIndex[cur.from]
        let dragIndex = this.ringList[cur.from].length - 1
        this.transition = "all 0.5s";
        this.dragOrder = this.ringList[cur.from][dragIndex].order
        await this.moveToNewPos(columnIndex, cur.from, dragIndex);
        this.dragToColumn(columnIndex, cur.from, dragIndex);
        this.transition = "none";
        this.dragPos.x = 0
        this.dragPos.y = 0
    }
}

至此胧沫,游戲的全部功能都已完成绒怨,源代碼已經(jīng)上傳到github:https://github.com/wanglin2/hannuota谦疾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市六剥,隨后出現(xiàn)的幾起案子峰伙,更是在濱河造成了極大的恐慌,老刑警劉巖策彤,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匣摘,死亡現(xiàn)場離奇詭異音榜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)囊咏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門梅割,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泌类,你說我怎么就攤上這事底燎。” “怎么了双仍?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵朱沃,是天一觀的道長。 經(jīng)常有香客問我搬卒,道長翎卓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任坯门,我火速辦了婚禮逗扒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘允瞧。我一直安慰自己蛮拔,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布畦韭。 她就那樣靜靜地躺著肛跌,像睡著了一般察郁。 火紅的嫁衣襯著肌膚如雪皮钠。 梳的紋絲不亂的頭發(fā)上赠法,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音款侵,去河邊找鬼侧纯。 笑死,一個胖子當(dāng)著我的面吹牛壕鹉,可吹牛的內(nèi)容都是我干的聋涨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼脊凰,長吁一口氣:“原來是場噩夢啊……” “哼茂腥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帕胆,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤懒豹,失蹤者是張志新(化名)和其女友劉穎驯用,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝴乔,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡薇正,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了雕沿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鞠鲜,死狀恐怖断国,靈堂內(nèi)的尸體忽然破棺而出稳衬,到底是詐尸還是另有隱情坐漏,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布街夭,位于F島的核電站躏筏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏埃碱。R本人自食惡果不足惜酥泞,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望似炎。 院中可真熱鬧悯姊,春花似錦、人聲如沸挠轴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邢隧。三九已至,卻和暖如春按摘,著一層夾襖步出監(jiān)牢的瞬間纫谅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工兰珍, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留询吴,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓唠摹,卻偏偏與公主長得像奉瘤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子望艺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容