js 实现小时候xp上自带的拼图

1. 游戏截图

    本图在网络中寻找的,突发奇想怀旧一下
    

2. 思路 

  1. 看到游戏面板是一个正方形,每列每行的方块都是相同的
  2. 有一个空白区域可供玩家进行换位,最终拼成图案
  3. 在空格上下左右方向的可以换位

3.  大框架

        大框架的宽高是由每列每行的小方块决定的,假设小方块宽等于50那高也等于50,每行4个小方块,那大框架的宽高就等于4*50

4. 代码

        1. 创建一个Game类并创建初始化变量

class Game {
  constructor() { 
    this.boxSize = 100; // 小格子的大小
    this.cvs = document.getElementById("canvas");
    this.ctx = this.cvs.getContext("2d");
    // 边距
    this.margin = {left:0,top:0}; // canvas元素的外边距
    this.level = 0;               // 关卡难度
    this.levels = [3,4,5];        // 每关对应的方块数量
    this.levelArr = [];           // 呈现在画布上的数组
    this.levelCopy = [];          // 成功的数据
  }
}

        2.  编写init方法

在这里我们插入了一个"zzz"代表空白处,levelArr就是levelCopy打乱后的结果

  
initLevel() {
   this.cvs.width = this.cvs.height = this.boxSize * this.levels[this.level];
   this.getMargin();
   this.levelCopy = [];
   this.levelArr = [];
   const levelNum = this.levels[this.level];
   const count = levelNum * levelNum - 1;
   this.levelCopy = Array.from({length: count}, (_, index) => index + 1);
   this.levelCopy.push('zzz');
   this.levelArr = this.shuffleArray([...this.levelCopy]);
   this.draw();
 }
 // 打乱数组
shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}

        3. 绘制levelArr

这里使用一维数组进行绘制,但是需要绘制的是正方形的结果,可以想到每列每行的数量都是根据“this.levels[this.level]”来决定的,假设val值为3,每次循环到"i % val === 0"就代表循环的一行,就需要换行渲染。

我使用pointer对象来记录每个方块渲染的位置。

结果图:

到这里绘制就基本差不多了

draw() {
    this.ctx.clearRect(0,0,this.cvs.width,this.cvs.height);
    this.drawRectLine(0,0,this.cvs.width,this.cvs.height);
    const levelNum = this.levels[this.level];
    const pointer = {x:0,y:0};
    for(let i = 1; i <= this.levelArr.length; i++){
      const {x,y} = pointer;
      this.drawRect(y * this.boxSize,x * this.boxSize,this.boxSize,this.boxSize,this.levelArr[i - 1]);
      pointer.y += 1;
      if(i % levelNum === 0){
        pointer.x += 1;
        pointer.y = 0;
      }
    }
  }
  drawRect(x,y,w,h,text){
    this.ctx.beginPath();
    if(text === 'zzz'){
      this.ctx.fillStyle = "rgba(255,255,255,1)";
    }else{
      this.ctx.fillStyle = "rgba(0,0,0,0.5)";
    }
    this.ctx.fillRect(x,y,w,h);
    if(text!== 'zzz'){
      this.ctx.fillStyle = "rgba(255,255,255,1)";
      this.ctx.font = "18px Arial";
      this.ctx.fillText(text,x + w / 2,y + h / 2);
    }
    this.ctx.strokeRect(x,y,w,h);
    this.ctx.closePath();
  }

        4. 添加点击事件

这里我们需要修改margin变量,在click方法中计算鼠标点击在canvas中的位置。

judgeMove方法中我们判断是否可以移动并移动

在这个判断中+1判断时的临近的,解决了左右移动,

看到图中8想要移动下来,1移动上去怎么判断呢

这是一维数组格式化后展示

[

        4,8,7,

        2,z,6,

        3,1,5

]

可以看到8的索引加上3就是z的索引,但是发现好像就是加上了每列的数量,获取到了此信息就可以判断了,假设索引等于4,判断4+3等不等于z的索引如果等于就可以移动

这样子基本功能就完成了。

  // 获取边距
  getMargin() {
    const {offsetLeft,offsetTop} = this.cvs;
    this.margin = {left:offsetLeft,top:offsetTop};
  } 
  addClick(){
    this.cvs.addEventListener("click",this.click);
  }
  // 处理点击事件
  click = (e) => {
    const {clientX,clientY} = e;
    const x = clientX - this.margin.left;
    const y = clientY - this.margin.top;
    this.getClickBox(x,y);
  }
  // 获取点击的方块
  getClickBox(x,y) {
    this.judgeMove(this.getIndex(x,y));
  }
  // 判断点击的方块是否可移动并移动
  judgeMove(obj) {
    const {x,y} = obj;
    const index = this.levels[this.level] * y + x; // 获取索引
    const zzzIndex = this.levelArr.findIndex(item => item === 'zzz');// 获取zzz索引
    const current = this.levelArr[index];
    const step = this.levels[this.level];
    if(current !== 'zzz'){
      if(index + 1 === zzzIndex || zzzIndex + 1 === index || index + step === zzzIndex || zzzIndex + step === index){
        this.levelArr[index] = 'zzz'; 
        this.levelArr[zzzIndex] = current; 
        this.draw();   
        this.judgeSuccess();
      }
    }
  }
  // 通过x,y获取方块的索引
  getIndex(x,y) {
    const obj = {x:'',y:''};
    const {left,top} = this.margin;
    const arr = Array.from({length: this.levels[this.level]},(_,index) => index * this.boxSize + this.boxSize);
    for(let i = 0; i < arr.length; i++){
      if(obj.x === '' && x < arr[i]){
        obj.x = i;
      }
      if(obj.y === '' && y < arr[i]){
        obj.y = i;
      }
    }
    return obj;
  }

5. 所有代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    *{
      margin: 0;
      padding: 0;
      list-style: none;
    }
    canvas{
      display: block;
      margin: auto;
    }
  </style>
</head>
<body>
  <canvas id="canvas"></canvas>
  <select name="level" id="select">
    <option value="0">简单</option>
    <option value="1">中等</option>
    <option value="2">困难</option>
  </select>
  <script>
    class Game {
      constructor() { 
        this.boxSize = 100;
        this.cvs = document.getElementById("canvas");
        this.ctx = this.cvs.getContext("2d");
        // 边距
        this.margin = {left:0,top:0};
        this.level = 0;
        this.levels = [3,4,5];
        this.levelArr = [];
        this.levelCopy = [];
        this.init();
      }
      init(){
        this.cvs.width = this.cvs.height = this.boxSize * this.levels[this.level];
        this.ctx.clearRect(0,0,this.cvs.width,this.cvs.height);
        this.initLevel();
        this.addClick();
      }
      // 获取边距
      getMargin() {
        const {offsetLeft,offsetTop} = this.cvs;
        this.margin = {left:offsetLeft,top:offsetTop};
      }
      drawRectLine(x,y,w,h,border) {
        this.ctx.beginPath()
        this.ctx.strokeStyle = "rgba(0,0,0,0.5)";
        this.ctx.strokeRect(x,y,w,h);
        this.ctx.closePath();
      }
      drawRect(x,y,w,h,text){
        this.ctx.beginPath();
        if(text === 'zzz'){
          this.ctx.fillStyle = "rgba(255,255,255,1)";
        }else{
          this.ctx.fillStyle = "rgba(0,0,0,0.5)";
        }
        this.ctx.fillRect(x,y,w,h);
        if(text!== 'zzz'){
          this.ctx.fillStyle = "rgba(255,255,255,1)";
          this.ctx.font = "18px Arial";
          this.ctx.fillText(text,x + w / 2,y + h / 2);
        }
        this.ctx.strokeRect(x,y,w,h);
        this.ctx.closePath();
      }
      // 打乱数组
      shuffleArray(array) {
        for (let i = array.length - 1; i > 0; i--) {
          const j = Math.floor(Math.random() * (i + 1));
          [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
      }
      // 设置难度
      setLevel(level) {
        this.level = level;
        this.initLevel();
      }
      // 关卡
      initLevel() {
        this.cvs.width = this.cvs.height = this.boxSize * this.levels[this.level];
        this.getMargin();
        this.levelCopy = [];
        this.levelArr = [];
        const levelNum = this.levels[this.level];
        const count = levelNum * levelNum - 1;
        this.levelCopy = Array.from({length: count}, (_, index) => index + 1);
        this.levelCopy.push('zzz');
        this.levelArr = this.shuffleArray([...this.levelCopy]);
        this.draw();
      }
      // 绘制
      draw() {
        this.ctx.clearRect(0,0,this.cvs.width,this.cvs.height);
        this.drawRectLine(0,0,this.cvs.width,this.cvs.height);
        const levelNum = this.levels[this.level];
        const pointer = {x:0,y:0};
        for(let i = 1; i <= this.levelArr.length; i++){
          const {x,y} = pointer;
          this.drawRect(y * this.boxSize,x * this.boxSize,this.boxSize,this.boxSize,this.levelArr[i - 1]);
          pointer.y += 1;
          if(i % levelNum === 0){
            pointer.x += 1;
            pointer.y = 0;
          }
        }
      }
      // 判断是否成功
      judgeSuccess() {
        const copy = JSON.stringify(this.levelCopy);
        const arr = JSON.stringify(this.levelArr);
        if(copy === arr){
          if(this.level == 2){
            alert('你通关了真哇塞');
            return;
          }else{
            alert('恭喜你,过关了');
            this.level += 1;
            this.initLevel();
          }
        }
      }
      // 判断点击的方块是否可移动并移动
      judgeMove(obj) {
        const {x,y} = obj;
        const index = this.levels[this.level] * y + x; // 获取索引
        const zzzIndex = this.levelArr.findIndex(item => item === 'zzz');// 获取zzz索引
        const current = this.levelArr[index];
        const step = this.levels[this.level];
        if(current !== 'zzz'){
          if(index + 1 === zzzIndex || zzzIndex + 1 === index || index + step === zzzIndex || zzzIndex + step === index){
            this.levelArr[index] = 'zzz'; 
            this.levelArr[zzzIndex] = current; 
            this.draw();   
            this.judgeSuccess();
          }
        }
      }
      // 通过x,y获取方块的索引
      getIndex(x,y) {
        const obj = {x:'',y:''};
        const {left,top} = this.margin;
        const arr = Array.from({length: this.levels[this.level]},(_,index) => index * this.boxSize + this.boxSize);
        for(let i = 0; i < arr.length; i++){
          if(obj.x === '' && x < arr[i]){
            obj.x = i;
          }
          if(obj.y === '' && y < arr[i]){
            obj.y = i;
          }
        }
        return obj;
      }
      // 获取点击的方块
      getClickBox(x,y) {
        this.judgeMove(this.getIndex(x,y));
      }
      // 处理点击事件
      click = (e) => {
        const {clientX,clientY} = e;
        const x = clientX - this.margin.left;
        const y = clientY - this.margin.top;
        this.getClickBox(x,y);
      }
      // 添加点击事件
      addClick(){
        this.cvs.addEventListener("click",this.click);
      }
    }
    let game;
    game = new Game();
    const select = document.getElementById('select');
    select.onchange = function(){
      game.setLevel(this.value);
    }
  </script>
</body>
</html>

6.效果展示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值