微信小程序实现的数字滑块拼图
【注】本文章只是记录下实现的过程,个人喜好,无技术指导倾向,感兴趣的小伙伴可以继续往下看下实现过程,
总体实现效果如下图,第一个界面是拼图初始界面,第二个是拼图过程界面
游戏玩法介绍:
滑块拼图(Slider Puzzle)是一种经典的智力游戏,通常由一个3x3或更大的格子组成,其中一个格子为空,玩家通过滑动拼图块来达到特定的图案或顺序。
按钮介绍
“开始/暂停” 按钮
初次进入页面,需要点击开始按钮,才能进行游戏,第一次点击后,屏幕上面的计时器会开始计时,再次点击暂停按钮,计时停止,游戏界面锁定
“重置”按钮
将正在进行中的游戏进行还原初始化,重新开始
代码实现
代码整体目录结构如下:
├── app.js
├── app.json
├── app.wxss
├── pages
│ ├── index
│ │ ├── index.js
│ │ ├── index.json
│ │ ├── index.wxml
│ │ └── index.wxss
├── project.config.json
├── project.private.config.json
├── sitemap.json
└── utils
└── util.js
感兴趣的小伙伴可以下载看下
源码下载:https://gitee.com/ZYHHYZ/slider_puzzles
app.json
该文件仅仅是定义了一个导航条名称为 滑块拼图
{
"pages": [
"pages/index/index"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "滑块拼图",
"navigationBarTextStyle": "black"
}
}
page
page文件夹 只包含 一个index页面
index页面
index.wxml
代码如下
<view class="container">
<!-- 计时器 -->
<view class="timer-row">
<view class="timer-text timer-text-m">{{ timerM }}</view>
<view class="timer-sp timer-text-sp">:</view>
<view class="timer-text timer-text-s">{{ timerS }}</view>
<view class="timer-sp timer-text-sp">:</view>
<view class="timer-text timer-text-ms">{{ timerMs }}</view>
</view>
<!-- 滑块 -->
<view class="puzzle-container">
<block wx:for="{{tiles}}" wx:key="*this">
<view class="puzzle-tile" bind:tap="onclickEvent" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd" data-index="{{index}}">
{{item}}
</view>
</block>
</view>
<!-- 按钮区 -->
<view class="button-container">
<button class="start-button" bindtap="toggleTimer">开始/停止</button>
<button class="restart-button" bindtap="restartPuzzle">重置</button>
</view>
</view>
index.js
Page({
data: {
successTiles: [1, 2, 3, 4, 5, 6, 7, 8, ''],
tiles: [1, 2, 3, 4, 5, 6, 7, 8, ''],
emptyIndex: 8,
selector: {
x: 0,
y: 0,
index: 0
},
isGameing: false,
timer: '00:00:00',
timerM: '00',
timerS: '00',
timerMs: '00',
timerRunning: false,
timerInterval: null
},
onLoad: function () {
},
touchStart: function (e) {
// console.log("移动开始", e.currentTarget.dataset)
if(!this.data.timerRunning){
console.log("请点击 开始按钮")
return
}
const touch = e.touches[0];
let index = e.currentTarget.dataset.index;
let xy = this.getXY(index, 3)
// console.log(xy)
this.setData({
startX: touch.clientX,
startY: touch.clientY,
selector: xy
});
},
touchMove: function (e) {
// console.log("移动进行中", e)
const touch = e.touches[0];
this.setData({
moveX: touch.clientX,
moveY: touch.clientY,
});
},
touchEnd: function (e) {
// console.log("移动结束", e)
const endX = this.data.moveX;
const endY = this.data.moveY;
const startX = this.data.startX;
const startY = this.data.startY;
const deltaX = endX - startX;
const deltaY = endY - startY;
// console.log("deltaX ", deltaX," deltaY ", deltaY)
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动
if (deltaX > 30) {
// 向右滑动
// console.log("→")
this.slideTiles('right');
} else if (deltaX < -30) {
// 向左滑动
// console.log("←")
this.slideTiles('left');
}
} else {
// 垂直滑动
if (deltaY > 30) {
// 向下滑动
// console.log("↓")
this.slideTiles('down');
} else if (deltaY < -30) {
// 向上滑动
// console.log("↑")
this.slideTiles('up');
}
}
},
slideTiles: function (direction) {
// 根据方向交换空白块和相邻块
// 这里需要添加实际的交换逻辑,确保空白块可以移动
// 例如,你可以在这里交换两个块的位置,但要确保空白块可以移动
// 以下是一个简单的示例,实际逻辑会更复杂
let newTiles = this.data.tiles.slice();
let emptyIndex = this.data.emptyIndex;
let selector = this.data.selector;
emptyIndex = selector.index
console.log(selector, direction)
switch (direction) {
case 'right':
if (emptyIndex % 3 < 2) {
// 判断相邻的要移动的是否为空白块
if (newTiles[emptyIndex + 1] != '') {
console.log("不能移动", direction)
return
}
[newTiles[emptyIndex], newTiles[emptyIndex + 1]] = [newTiles[emptyIndex + 1], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex + 1
});
}
break;
case 'left':
if (emptyIndex % 3 > 0) {
if (newTiles[emptyIndex - 1] != '') {
console.log("不能移动", direction)
return
}
[newTiles[emptyIndex], newTiles[emptyIndex - 1]] = [newTiles[emptyIndex - 1], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex - 1
});
}
break;
case 'up':
if (emptyIndex >= 3) {
if (newTiles[emptyIndex - 3] != '') {
console.log("不能移动", direction)
return
}
[newTiles[emptyIndex], newTiles[emptyIndex - 3]] = [newTiles[emptyIndex - 3], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex - 3
});
}
break;
case 'down':
if (emptyIndex < 6) {
if (newTiles[emptyIndex + 3] != '') {
console.log("不能移动", direction)
return
}
[newTiles[emptyIndex], newTiles[emptyIndex + 3]] = [newTiles[emptyIndex + 3], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex + 3
});
}
break;
}
this.isSuccess()
},
isSuccess: function () {
let ti = this.data.tiles
let su = this.data.successTiles
console.log(su == ti)
let ti_str = ti.join(',')
let su_str = su.join(',')
// console.log(ti_str == su_str)
// console.log("isSuccess", ti, ti_str, su_str)
if (ti_str == su_str) {
this.stopTimer();
wx.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
});
}
},
shuffleArray: function (array) {
for (let i = array.length - 1; i > 0; i--) {
// 生成一个随机索引从 0 到 i
const j = Math.floor(Math.random() * (i + 1));
// 交换元素
[array[i], array[j]] = [array[j], array[i]];
}
return array;
},
onclickEvent: function (e) {
if(!this.data.isGameing){
console.log("请点击 开始按钮")
return
}
let index = e.currentTarget.dataset.index;
let newTiles = this.data.tiles.slice();
let tiles = this.data.tiles
let currentTile = tiles[index]
if (currentTile == "") {
// console.log("不能移动")
return
}
let upIndex, downIndex, leftIndex, rightIndex = null;
// 查询 上下左右的索引
let xy = this.getXY(index, 3)
let selector = this.data.selector;
let emptyIndex = selector.index
// 可以向右移动
if (emptyIndex % 3 < 2) {
if (newTiles[emptyIndex + 1] == '') {
[newTiles[emptyIndex], newTiles[emptyIndex + 1]] = [newTiles[emptyIndex + 1], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex + 1
});
}
}
// 可以向左运动
if (emptyIndex % 3 > 0) {
if (newTiles[emptyIndex - 1] == '') {
[newTiles[emptyIndex], newTiles[emptyIndex - 1]] = [newTiles[emptyIndex - 1], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex - 1
});
}
}
// 可以向上运动
if (emptyIndex >= 3) {
if (newTiles[emptyIndex - 3] == '') {
[newTiles[emptyIndex], newTiles[emptyIndex - 3]] = [newTiles[emptyIndex - 3], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex - 3
});
}
}
// 向下运动
if (emptyIndex < 6) {
if (newTiles[emptyIndex + 3] == '') {
[newTiles[emptyIndex], newTiles[emptyIndex + 3]] = [newTiles[emptyIndex + 3], newTiles[emptyIndex]];
this.setData({
tiles: newTiles,
emptyIndex: emptyIndex + 3
});
}
}
console.log("点击事件", xy, )
},
getXY: function (index, num) {
let x = Math.floor(index / num) + 1
let y = index % num + 1
return {
x,
y,
index
}
},
startTimer: function () {
if (!this.data.timerRunning) {
let tiles = this.data.tiles
let isGameing = this.data.isGameing
if (!this.data.isGameing) {
// 游戏未开始 ,打乱顺序
tiles = this.shuffleArray(tiles)
isGameing = true
}
this.setData({
tiles: tiles,
timerRunning: true,
isGameing: isGameing
});
this.data.timerInterval = setInterval(() => {
let timers = this.data.timer.split(":")
let m = Number(timers[0])
let s = Number(timers[1])
let ms = Number(timers[2])
if (ms < 99) {
ms += 1
} else {
ms = 0
if (s < 59) {
s = s + 1
} else {
s = 0
m += 1
}
}
let timestr = `${this.formatNumber(m,2)}:${this.formatNumber(s,2)}:${this.formatNumber(ms,2)}`
console.log("--------", timers, timestr)
this.setData({
timer: timestr,
timerM:this.formatNumber(m,2),
timerS:this.formatNumber(s,2),
timerMs:this.formatNumber(ms,2)
});
}, 10);
}
},
stopTimer: function () {
if (this.data.timerRunning) {
this.setData({
timerRunning: false
});
clearInterval(this.data.timerInterval);
}
},
toggleTimer: function () {
if (this.data.timerRunning) {
this.stopTimer();
} else {
this.startTimer();
}
},
restartPuzzle:function(){
// let tiles = this.data.tiles
// tiles = this.shuffleArray(tiles)
this.stopTimer()
this.setData({
successTiles: [1, 2, 3, 4, 5, 6, 7, 8, ''],
tiles: [1, 2, 3, 4, 5, 6, 7, 8, ''],
emptyIndex: 8,
selector: {
x: 0,
y: 0,
index: 0
},
isGameing: false,
timer: '00:00:00',
timerM: '00',
timerS: '00',
timerMs: '00',
timerRunning: false,
timerInterval: null
})
// this.startTimer()
},
formatNumber: function (num, length) {
return num.toString().padStart(length, '0');
},
});
index.wxss
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #fffaf0;
font-family: 'Comic Sans MS', cursive, sans-serif;
}
.timer-row {
display: flex;
align-items: center;
margin-bottom: 100px;
animation: blink 2s infinite;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #ffb6c1;
border-radius: 15px;
padding: 15px;
}
.timer-text {
width: 90px;
height: 100px;
background-color: #ffcccc;
display: flex;
justify-content: center;
align-items: center;
font-size: 64px; /* 增大字体大小 */
font-weight: bold; /* 加粗字体 */
cursor: pointer;
position: relative;
overflow: hidden;
transition: transform 0.3s ease;
/* border-radius: 5px; */
color: #ffffff; /* 粉色调字体 */
}
.timer-sp {
width: 10px;
height: 100px;
background-color: #ffcccc;
display: flex;
justify-content: center;
align-items: center;
font-size: 64px; /* 增大字体大小 */
font-weight: bold; /* 加粗字体 */
position: relative;
color: #ffffff; /* 粉色调字体 */
}
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* */
.button-container {
display: flex;
justify-content: space-between; /* 使按钮分布在容器的两端 */
width: 100%; /* 容器宽度为100% */
margin-top: 50px; /* 与拼图块之间添加一些间距 */
}
.start-button {
background-color: hsl(0, 100%, 90%);
color: #cf77a3;
width: 110px;
border: 1px solid #ffb6c1;
padding: 5px 10px;
border-radius: 5px;
font-size: 18px;
font-weight: bolder;
cursor: pointer;
}
.restart-button {
background-color: #ffcccc;
color: #cf77a3;
border: 1px solid #ffb6c1;
padding: 5px 10px;
width: 110px;
border-radius: 5px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
}
/* 滑块样式 */
.puzzle-container {
margin-top: -50px;
display: grid;
grid-template-columns: repeat(3, 100px);
grid-gap: 10px;
perspective: 1000px;
}
.puzzle-tile {
width: 100px;
height: 100px;
background-color: #ffcccc;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
font-weight: bold;
cursor: pointer;
position: relative;
overflow: hidden;
border: 1px solid #ffb6c1;
transition: transform 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #ff69b4;
}
.puzzle-tile {
width: 100px;
height: 100px;
background-color: #ffcccc;
display: flex;
justify-content: center;
align-items: center;
font-size: 64px; /* 增大字体大小 */
font-weight: bold; /* 加粗字体 */
cursor: pointer;
position: relative;
overflow: hidden;
border: 1px solid #ffb6c1;
transition: transform 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #ffffff; /* 粉色调字体 */
}
/* 当拼图块被点击时,添加一些动画效果 */
.puzzle-tile:active {
transform: scale(0.5);
}