微信小程序实现的数字滑块拼图

微信小程序实现的数字滑块拼图

【注】本文章只是记录下实现的过程,个人喜好,无技术指导倾向,感兴趣的小伙伴可以继续往下看下实现过程,

总体实现效果如下图,第一个界面是拼图初始界面,第二个是拼图过程界面

下面图面中,第一个是初始界面,第二个是拼图过程界面

游戏玩法介绍:

滑块拼图(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);
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值