Skip to content

填字游戏

起初是公司一个小游戏,从立项到开发完成大概5天吧,因为时间有些赶,最开始写的并不是很好,画字格用的是 table。 UI上面小小出入,跟一个朋友说做这个项目后,朋友建议我用 pixijs,利用闲暇时间重写了一遍,下面是源码

HTML

html
<p class="time-container"><time id="time">00:00</time></p>
<div class="game-container">
  <canvas id="game" width="500" height="500"></canvas>
  <div class="mask">初始化中...</div>
</div>
<div class="tips-container">
  <p class="fl">提示:</p>
  <p id="tips" class="oh">赫拉利将告诉你人类是如何从动物变成上帝</p>
</div>
<div class="input-container">
  <input type="text" class="game-input" id="game-input" />
  <a href="javascript:;" class="game-input-send">确认</a>
</div>

JavaScript

js
require(['zepto', 'pixi', 'modal'], function ($, pixi, modal) {
  // 游戏基础配置
  modal.pupup({
    content: `
      <!-- <img src="http://sunday90.cn/upload/2500872220b52768dd9c4e237579f8bc.jpg" /> -->
      <a href="javascript:;" class="btn start-btn">开始计时</a>
    `,
    mask: true,
    success: function (layero) {
      const that = this
      $(layero).on('click', '.start-btn', function (e) {
        console.log(this)
        console.log('开始游戏')
        let time = document.querySelector('#time')
        Countdown.start(0.2, time, function (time) {
          console.log(time)
        })
        that.close()
      })
    }
  })

  const ua = window.navigator.userAgent.toLocaleLowerCase()
  const device = {
    ios: /iphone/g.test(ua),
    android: /android/g.test(ua)
  }

  const unit_size = { width: 50, height: 50 }
  const table = { x: 10, y: 10 }
  const gameContainer = document.querySelector('.game-container')
  const mask = document.querySelector('.mask')
  const input = document.querySelector('#game-input')
  const tipsEl = document.querySelector('#tips')
  const fontStyle = new PIXI.TextStyle({
    fontFamily:
      '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
    fontSize: 28,
    fontWeight: '500',
    fill: ['#333'] // gradient
  })
  const time = document.querySelector('#time')
  // 需要渲染的方格二维数组
  const unit_data = [
    [
      { y: 0, x: 1 },
      { y: 0, x: 5 },
      { y: 0, x: 6 },
      { y: 0, x: 7 },
      { y: 0, x: 8 }
    ],
    [
      { y: 1, x: 1 },
      { y: 1, x: 3 },
      { y: 1, x: 8 }
    ],
    [
      { y: 2, x: 1 },
      { y: 2, x: 2 },
      { y: 2, x: 3 },
      { y: 2, x: 4 },
      { y: 2, x: 6 },
      { y: 2, x: 7 },
      { y: 2, x: 8 }
    ],
    [
      { y: 3, x: 3 },
      { y: 3, x: 7 }
    ],
    [
      { y: 4, x: 0 },
      { y: 4, x: 1 },
      { y: 4, x: 2 },
      { y: 4, x: 3 },
      { y: 4, x: 6 },
      { y: 4, x: 7 },
      { y: 4, x: 8 },
      { y: 4, x: 9 }
    ],
    [
      { y: 5, x: 1 },
      { y: 5, x: 7 }
    ],
    [
      { y: 6, x: 1 },
      { y: 6, x: 4 },
      { y: 6, x: 5 },
      { y: 6, x: 6 },
      { y: 6, x: 7 }
    ],
    [
      { y: 7, x: 1 },
      { y: 7, x: 4 }
    ],
    [
      { y: 8, x: 1 },
      { y: 8, x: 2 },
      { y: 8, x: 3 },
      { y: 8, x: 4 }
    ],
    [
      { y: 9, x: 0 },
      { y: 9, x: 1 }
    ]
  ]

  // tips
  const tips = {
    21222324: '赫拉利将告诉你人类是如何从动物变成上帝',
    40414243: '对两次世界大战的哲学思考,直面存在的危机',
    81828384: '看萧红写的这书名还以为是一条河的历史',
    9091: '鲁迅先生的短篇小说集,里面有《狂人日记》',
    64656667: '天下合久必分,分久必合',
    46474849: '与达尔文《物种起源论》及哥白尼《天体运行论》并称为人类三大思想革命的经典之作',
    262728: '三位艺术天才的非凡人生,罗曼罗兰著',
    5678: '证明你上过中学的又一个有力佐证,冰心奶奶如是说',
    11121: '我们都是故事外的人——阿尔贝·加缪',
    13233343: '一种追求简单生活的价值观',
    415161718191: '余华手下一本关于记忆的长篇小说',
    647484: '走遍万水千山,泪尽撒哈拉',
    2737475767: '达康书记别低头,皇冠会掉',
    81828: '中国四大名著之一'
  }

  // 创建 12 * 8 的方格
  const unit = [] // 所有单元格 二维数组
  const unit_block = [] // 需要渲染的单元格
  const selectUnit = { isRow: true, unit: [] } // 选中后的单元格

  // 创建材质
  const unitTexture = PIXI.Texture.fromImage('./assets/images/unit.svg')
  const unitBlockTexture = PIXI.Texture.fromImage('./assets/images/unit-block.svg')
  const unitBlockActiveTexture = PIXI.Texture.fromImage('./assets/images/unit-block-active.svg')

  // 创建舞台
  const container = new PIXI.Container()
  container.x = 0
  container.y = 0

  // 倒计时
  const Countdown = {
    down(callBack, success) {
      clearInterval(this.Interval)
      callBack(success === true ? this.countTime * 60 - this.time / 1000 : '时间结束')
    },
    check(v) {
      if (v < 10) {
        return '0' + v
      } else {
        return v
      }
    },
    start(time, el, callBack) {
      this.countTime = time
      var endDate = new Date().getTime() + time * 60 * 1000
      this.Interval = setInterval(() => {
        this.time = endDate - new Date().getTime()
        if (this.time <= 0) this.down(callBack)
        var m = parseInt((this.time / 1000 / 60) % 60, 10)
        var s = parseInt((this.time / 1000) % 60, 10)
        var ms = parseInt(this.time % 1000, 10)
        el.innerText = this.check(m) + ':' + this.check(s)
      }, 1)
    }
  }

  // es7
  setTimeout(() => {
    initGame()
  }, 300)

  async function initGame() {
    await (() => {
      return new Promise((resolve, reject) => {
        mask.classList.add('mask-in')
        setTimeout(() => {
          resolve()
        }, 300)
      })
    })()

    // 创建画布
    const app = new PIXI.Application(table.x * unit_size.width, table.y * unit_size.height, {
      backgroundColor: 0xf1c16c,
      antialias: true,
      view: document.querySelector('#game'),
      resolution: window.devicePixelRatio
    })
    app.stage.interactive = true
    app.stage.addChild(container)

    // 绘制表格
    creatTable(table, unit_data, async function () {
      await (() => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            mask.innerText = '初始化完成,祝您玩的开心'
            resolve()
          }, 1000)
        })
      })()

      await setTimeout(() => {
        mask.classList.add('mask-out')
        console.log('初始化完成')
      }, 500)
    })
  }

  function creatTable(table, block, callBack) {
    let row = table.x
    let col = table.y
    let index = 0
    let width = unit_size.width * table.x * unit_size.width
    let height = unit_size.width * table.y * unit_size.height

    let i = 0
    while (i < row) {
      unit.push([])
      let k = 0
      while (k < col) {
        let sprite = new PIXI.Sprite(unitTexture)
        let unitContainer = new PIXI.Container()
        let x = (k * unit_size.width) % width
        let y = (i * unit_size.height) % height
        sprite.interactive = true
        sprite.isDown = false
        sprite.isEmpty = true
        sprite.anchor.set(0, 0)
        sprite.width = unit_size.width
        sprite.height = unit_size.height
        sprite.index = index++ // 唯一id
        unitContainer.width = unit_size.width
        unitContainer.height = unit_size.height
        unitContainer.x = x
        unitContainer.y = y
        sprite.x = 0
        sprite.y = 0
        sprite.X = x / unit_size.width // x坐标
        sprite.Y = y / unit_size.height // y 坐标
        unit[i].push(sprite)
        unitContainer.addChild(sprite)
        container.addChild(unitContainer)
        k++
      }
      i++
    }

    // 添加事件
    for (let row = 0, uLen = unit.length; row < uLen; row++) {
      add: for (let col = 0, len = unit[row].length; col < len; col++) {
        // console.log(unit[row][col].x / unit_size.width, unit[row][col].y / unit_size.height);
        for (let k = 0, fLen = block[row].length; k < fLen; k++) {
          if (block[row][k].x === col && block[row][k].y === row) {
            unit[row][col].isEmpty = false
            unit[row][col].texture = unitBlockTexture
            unit[row][col].buttonMode = true
            unit[row][col].on('tap', onClick)
            unit_block.push(unit[row][col])
            continue add
          }
        }
      }
    }
    // 绘制边框
    drawBorder(table, unit_data, container)
    typeof callBack === 'function' ? callBack() : void 0
  }

  function onClick() {
    input.focus()
    const X = this.X,
      Y = this.Y
    let cols = [],
      rows = unit[Y]
    // 重置默认
    reset(unit_block)

    this.isDown = !this.isDown
    unit.forEach(row => {
      if (row[X].X === X) cols.push(row[X])
    })

    if (this.selectRow !== undefined) {
      if (this.isDown && this.selectRow) {
        renderCol(X, Y, cols, rows)
      } else {
        renderRow(X, Y, rows, cols)
      }
    } else {
      if (this.isDown) {
        renderRow(X, Y, rows, cols)
      } else {
        renderCol(X, Y, cols, rows)
      }
    }

    let key = selectUnit.unit.reduce((total, unit) => {
      return total + unit.index.toString()
    }, '')
    tipsEl.innerText = tips[key]
    console.log(key)
  }

  // 渲染行
  function renderRow(x, y, row, col) {
    let unit = aroundUnit(row, x, y, true)
    let len = unit.length
    // 如果临近元素为为空 (1 代表自身) 渲染列
    if (len <= 1) {
      renderCol(x, y, col, row)
      return
    }

    unit.forEach(item => {
      item.texture = unitBlockActiveTexture
      item.selectRow = true
    })

    let storUnit = stor(unit)
    selectUnit.isRow = true
    selectUnit.unit = storUnit
  }

  // 渲染列
  function renderCol(x, y, col, row) {
    let unit = aroundUnit(col, x, y, false)
    let len = unit.length
    // 如果临近元素为空 (1 代表自身) 渲染行
    if (len <= 1) {
      renderRow(x, y, row, col, true)
      return
    }
    unit.forEach(item => {
      item.texture = unitBlockActiveTexture
      item.selectRow = false
    })

    let storUnit = stor(unit)
    selectUnit.isRow = false
    selectUnit.unit = storUnit
  }

  // 获取相邻元素  arry 一维数组 行 或者 列
  function aroundUnit(arry, x, y, selectRow) {
    let n = selectRow ? x : y
    let len = arry.length
    let unit = []
    for (let i = 0; i < len; i++) {
      if (arry[n + i] && arry[n + i].isEmpty) break
      if (arry[n + i]) {
        unit.push(arry[n + i])
      }
    }

    for (let i = 1; i < len; i++) {
      if (arry[n - i] && arry[n - i].isEmpty) break
      if (arry[n - i]) unit.push(arry[n - i])
    }
    return unit
  }

  // 重置视图
  function reset(unit) {
    unit.forEach(item => {
      item.isDown = false
      item.texture = unitBlockTexture
    })
  }

  // 排序
  function stor(unit) {
    function compare(property) {
      return function (a, b) {
        var value1 = a[property]
        var value2 = b[property]
        return value1 - value2
      }
    }
    return unit.sort(compare('index'))
  }

  function drawBorder(table, block, ctx) {
    let row = table.x,
      col = table.y
    let containerWidth = row * unit_size.width,
      containerHeight = col * unit_size.height
    let startY = unit_size.height,
      startX = unit_size.width
    drawLine({ width: 1, color: 0xb38743, alpha: 1 }, ctx, containerWidth, containerHeight, startX, startY, row, col)
  }

  // 绘制线条
  function drawLine(lineStyle, ctx, width, height, X, Y, col, row) {
    let i = 1,
      n = 1
    const graphics = new PIXI.Graphics()
    // 绘制行
    while (i < row) {
      graphics.beginFill()
      graphics.lineStyle(lineStyle.width, lineStyle.color, lineStyle.alpha)
      graphics.moveTo(0, Y * i)
      graphics.lineTo(width, Y * i)
      graphics.endFill()
      ctx.addChild(graphics)
      i++
    }
    // 绘制列
    while (n < col) {
      graphics.beginFill()
      graphics.lineStyle(lineStyle.width, lineStyle.color, lineStyle.alpha)
      graphics.moveTo(X * n, 0)
      graphics.lineTo(X * n, height)
      graphics.endFill()
      ctx.addChild(graphics)
      n++
    }
  }

  // 解决键盘遮住 输入框

  $(document).on('focus', 'input', function (e) {
    setTimeout(
      () => {
        // this.scrollIntoViewIfNeeded(false);
        this.scrollIntoView({
          block: 'end',
          behavior: 'smooth'
        })
      },
      device.ios ? 200 : 0
    )
  })

  // 输入
  $('.input-container').on('click', '.game-input-send', async function (e) {
    let value = $.trim(input.value)
    if (value.length > selectUnit.unit.length) {
      modal.toast(`最大可输入字数为${selectUnit.unit.length}个`)
      return false
    }
    let param = selectUnit.unit.map((item, index) => {
      return {
        value: value.charAt(index),
        x: item.X,
        y: item.Y,
        index: item.index
      }
    })
    console.log(param)
    let flag = await new Promise((resolve, reject) => {
      $.ajax({
        async: true,
        type: 'post',
        url: './game.php',
        data: { data: param },
        success: function (data) {
          if (data.status === '0') {
            modal.toast('填错了哦')
            input.value = ''
            resolve(false)
          } else {
            resolve(true)
          }
        },
        error: function (err) {
          console.log(err)
        }
      })
    })

    if (!flag) return

    ;[...value].forEach((chart, index) => {
      if (selectUnit.unit[index].parent.children.length > 1) {
        selectUnit.unit[index].parent.children.forEach((sprite, i) => {
          if (sprite.type === 'text') sprite.text = chart
        })
        return false
      }

      let richText = new PIXI.Text(chart, fontStyle)
      richText.type = 'text'
      richText.anchor.set(0.5)
      richText.x = selectUnit.unit[index].parent.width / 2
      richText.y = selectUnit.unit[index].parent.height / 2
      selectUnit.unit[index].parent.addChild(richText)
      // container.addChild(richText);
    })
    input.value = ''
  })
})

PHP

后端就比较容易了,前端传每一个字格 id 以及值,后端验证字格 id 值 就行了

php
/*
* @Author: Edward
* @Date: 2018-05-02 23:27:31
* @Last Modified by: Edward
* @Email: 809537981@qq.com
* @Last Modified time: 2018-05-03 16:26:08
*/

header('Content-Type:application/json; charset=utf-8');

$post = $_POST['data'];

$data = [
  '1' => '局',
  '11' => '外',
  '5' => '繁',
  '6' => '星',
  '7' => '春',
  '8' => '水',
  '13' => '极',
  '33' => '主',
  '18' => '浒',
  '21' => '人',
  '22' => '类',
  '23' => '简',
  '24' => '史',
  '28' => '传',
  '27' => '人',
  '26' => '名',
  '37' => '民',
  '46' => '梦',
  '47' => '的',
  '57' => '名',
  '48' => '解',
  '49' => '析',
  '64' => '三',
  '65' => '国',
  '66' => '演',
  '67' => '义',
  '67' => '义',
  '74' => '毛',
  '81' => '呼',
  '82' => '兰',
  '83' => '河',
  '84' => '传',
  '40' => '存',
  '41' => '在',
  '42' => '主',
  '43' => '义',
  '51' => '细',
  '61' => '雨',
  '71' => '中',
  '81' => '呼',
  '90' => '呐',
  '91' => '喊',
];

$flag = false;

foreach($post as $index => $arry) {
  if ($data[$arry['index']] == $arry['value']) {
    $flag = true;
  } else {
    $flag = false;
  }
}

if ($flag) {
  echo '{"status": "1"}';
} else {
  echo '{"status": "0"}';
}

收工

最近更新