使用广度优先算法,求解拼图游戏的最少步骤数
目前只能支持简单移动和较少的步骤
使用dfs会不会更好点。。。。。
辅助工具类
在求解时,需要判断新的状态是否已经存在于队列中,如果存在则放弃,因为表明有更短的步骤
游戏界面稍微修改下即可
获取到路径数组后,设置定时器,每隔一段时间就移动一次
Num.js
// 返回数组中16(空白块所在位置)
function zero(items) {
return items.indexOf(16)
}
// 返回交换后的新数组
function swap(items, x, y) {
let items2 = items.map(item => item)
let t = items2[x]
items2[x] = items2[y]
items2[y] = t
return items2
}
// 数组是否已经满足条件
function isPass(items) {
return items.every(
(item, index) => item == index + 1
)
}
// 比较两个数组是否完全相等
function equal(item1, item2) {
return item1.length == item2.length && item1.every((i, index) => i == item2[index])
}
function bfs(items) {
let q = []
// 队列中每个元素有两个数据,一个是数组,一个是该元素的前一个元素
q.push({
items: items,
pre: -1
})
let path = []
let front = 0
let tail = 1
while (front < tail) {
let t = q[front]
// 如果满足条件
if (isPass(t.items)) {
path.push(t)
// console.log('success', t)
while (t.pre != -1) {
t = q[t.pre]
// console.log(t)
path.push(t)
}
return path.reverse()
}
// 继续遍历,上右下左,四个方向
/* for (let i = 0; i < 4; i++) {
q[tail++] = {
items: move(t.items, i),
pre: front
}
}*/
// 减少不必要的状态,只将有效的不重复的状态放入队列
for (let dir = 0; dir < 4; dir++) {
let nitems = false
if (dir == 0 && zero(t.items) > 3) {
// console.log('up', zero(t.items))
nitems = swap(t.items, zero(t.items), zero(t.items) - 4)
} else if (dir == 1 && zero(t.items) % 4 != 3) {
// console.log('right', zero(t.items))
nitems = swap(t.items, zero(t.items), zero(t.items) + 1)
} else if (dir == 2 && zero(t.items) <= 11) {
// console.log('down', zero(t.items))
nitems = swap(t.items, zero(t.items), zero(t.items) + 4)
} else if (dir == 3 && zero(t.items) % 4 != 0) {
// console.log('left')
nitems = swap(t.items, zero(t.items), zero(t.items) - 1)
}
// 如果队列中出现过的状态,则不用再次放入
// true 表示可以放入,即q中的每一个元素都与新状态不相等
let flag = !nitems ? false : q.every(
(item) => {
return !equal(Array.from(item.items), nitems)
}
)
if (nitems && flag) {
q[tail++] = {
items: nitems,
pre: front
}
}
}
front++
}
return path.reverse()
}
export default bfs
Game.js
<template>
<div class="game">
<transition-group name="cells" tag="div" class="grid">
<div class="cell" v-for="(i,index) in items" :key="i" @click="click(i,index)">{{i==16?'':i}}</div>
</transition-group>
<div class="egg" v-show="show">
{{egg}}
</div>
<div class="egg" v-show="isPass">
<h3>恭喜拼图成功 </h3>
</div>
<button @click="init" class="reset">重置</button>
<button @click="autoPlay" class="reset">自动</button>
</div>
</template>
<script>
import bfs from './Num'
export default {
name: "game-card",
data() {
return {
egg: 'hwq,你知道吗,我喜欢你很久了',
items: Array.from(Array(16)).map((_, index) => index + 1),
// items: [5, 2, 1, 13, 6, 3, 14, 7, 8, 9, 10, 11, 12, 15, 16, 4],
showEgg: false,
show: false,
}
},
watch: {
items() {
console.log('watch', this.items)
if (this.isPass) {
console.log('pass')
}
console.log(this.isEgg)
if (this.isEgg && !this.showEgg) {
this.showEgg = true
this.show = true
setTimeout(
() => this.show = false,
1000
)
}
}
},
computed: {
zero() {
return this.items.indexOf(16)
},
isPass() {
return this.items.every(
(item, index) => item == index + 1
)
},
isEgg() {
console.log(this.items.join(''))
return this.items.join('').indexOf('5211314') != -1
}
},
methods: {
getPath() {
// console.log('mounted')
let path = bfs(this.items)
// console.log(path)
return path
},
autoPlay() {
console.log('正在计算路径....')
let path = this.getPath()
console.log(`计算完毕...开始移动...最少需要${path.length-1}步`)
let i = 1
console.log('path', path)
this.items = path[i++].items
let inv = setInterval(
() => {
this.items = path[i].items
i++
if (i == path.length)
clearInterval(inv)
},
1000
)
},
// 交换x,y 位置变量
swap(x, y) {
let t = this.items[x]
this.items[x] = this.items[y]
this.items[y] = t
// this.$set(this.items, '' + y, t)
let arr = Array.from(this.items)
this.items = arr
},
// 0,1,2,3 表示上右下左,四个方向
move(dir) {
if (dir == 0 && this.zero > 3) {
console.log('up', this.zero)
this.swap(this.zero, this.zero - 4)
} else if (dir == 1 && this.zero % 4 != 3) {
console.log('right', this.zero)
this.swap(this.zero, this.zero + 1)
} else if (dir == 2 && this.zero <= 11) {
console.log('down', this.zero)
this.swap(this.zero, this.zero + 4)
} else if (dir == 3 && this.zero % 4 != 0) {
console.log('left')
this.swap(this.zero, this.zero - 1)
}
},
// 随机初始化
init() {
let num = parseInt(Math.random() * 1000)
console.log('init', num)
for (let i = 0; i < num; i++) {
let d = parseInt((Math.random() * 100)) % 4
this.move(d)
console.log(d)
}
},
click(item, index) {
console.log('click', item, index)
if (item == 16)
return
if (index + 1 < 16 && this.items[index + 1] == 16) {
console.log('click move', 3)
this.move(3)
} else if (index - 1 >= 0 && this.items[index - 1] == 16) {
this.move(1)
console.log('click move', 1)
} else if (index + 4 < 16 && this.items[index + 4] == 16) {
this.move(0)
console.log('click move', 0)
} else if (index - 4 >= 0 && this.items[index - 4] == 16) {
this.move(2)
console.log('click move', 2)
}
}
},
}
</script>
<style scoped>
.grid {
position: relative;
display: grid;
grid-gap: 5px;
background-color: rgb(233, 233, 233);
width: 215px;
border: 5px rgb(233, 233, 233) solid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
.cell {
background-color: lightskyblue;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
/*border: 5px solid white;*/
box-sizing: border-box;
}
/*过渡时间*/
.cells-move {
transition: transform 1s;
}
.cells-enter-active, .cells-leave-active {
transition: all 1s;
}
/*效果*/
.cells-enter, .cells-leave-to {
opacity: 0;
transform: translateY(30px);
}
.game {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.egg {
position: absolute;
bottom: 50px;
background: rgba(0, 0, 0, 0.5);
/*opacity: 0.8;*/
border-radius: 20px;
padding: 10px;
color: white;
}
.reset {
margin-top: 20px;
}
</style>