月饼被藏起来了!用指令操作小兔子来找月饼吧~

174 阅读3分钟

我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!

前言

这是一个我用来回顾Vue3+vite做的小游戏,因为我自从学习过Vue3后一直在使用Vue2全家桶,几乎已经把Vue3的东西忘完了,为了能尽快重新拿起新技术,同时也为了参加掘金的活动,花了一些时间做了这个小游戏,用代码操作小兔子是玩掘金的那个挖矿游戏想到的

整体代码量不算小也不算大,由于我第一次自己上手敲Vue3多少有点不适应,因此代码可能有点臃肿

介绍

  1. 整个小游戏使用的是Vue3制作
  2. 兔子固定被生成在左上角
  3. 月饼的位置每一局都是随机的
  4. 每一条指令需要按下回车才会开始读取与执行
  5. 若同时输入多个指令,则仅会执行第一条指令
  6. 共有5条指令,功能与写法如下表
指令功能示例
/take让兔子挖掘/take
/up (int)让兔子向上移动指定个单位/up 1
/right (int)让兔子向右移动指定个单位/right 1
/down (int)让兔子向下移动指定个单位/down 1
/left (int)让兔子向左移动指定个单位/left 1

关于挖掘

  • 每一次让兔子挖掘,若月饼不在挖掘范围内(0.5个单位),则兔子会探测一次兔子与月饼的距离并输出,反之则游戏成功
  • 兔子与月饼的距离是使用勾股定理计算得出的,但由于JavaScript在计算浮点数时有缺陷,因此输出的结果是经过了两次floor约等后得出的

关于移动

  • 为了游戏运行稳定,请尽量不要让兔子移动非整数个单位(感觉会出问题,但是目前还没有)
  • 兔子跑得很快,于是1个单位是10个像素(hhh)

运行图

屏幕截图 2022-09-16 173507.png

码上掘金演示

注意:为了得到正常的演示效果,请在体验前收起左侧代码栏

代码解释

template

<template>
  <div class="outer">
    <div class="game">
      <div class="player" ref="player"></div>
      <div class="cake" ref="cake" v-show="gameFinish"></div>
    </div>
    <div class="text">
      <div class="textArea" v-html="history" ref="textArea"></div>
      <input type="text" @keyup.enter="getCode" v-model="input">
    </div>
  </div>
</template>

style

<style scoped>
  .player {
    height: 20px;
    width: 20px;
    border: 1px solid pink;
    color: pink;
    text-align: center;
    line-height: 20px;
    position: absolute;
  }

  .cake {
    height: 20px;
    width: 20px;
    border: 1px solid orange;
    color: orange;
    text-align: center;
    line-height: 20px;
    position: absolute;
  }

  .outer {
    display: flex;
    flex-direction: row;
  }

  .game {
    position: relative;
    height: 300px;
    width: 300px;
    border: 1px solid #000;
  }

  .text {
    height: 300px;
    width: 300px;
  }

  .textArea {
    height: 280px;
    border: 1px solid #000;
    overflow-x: auto;
  }

  input {
    width: 294px;
    margin: 0;
  }
</style>
  • 游戏界面加上了position: relative,兔子、月饼加上了position: absolute属性,方便控制其位置
  • HTML与CSS没有什么内容,下面将详细解释script部分

解析输入

let input = ref('')
let textArea = ref(null)
let coordinate = [0, 0]
let gameFinish = ref(false)
let cake = ref(null)
let cakeLocation = [Math.floor(Math.random() * (300 - 20)), Math.floor(Math.random() * (300 - 20))]
onMounted(() => {
  console.log(player.value);
  cake.value.style.top = cakeLocation[0] + 'px'
  cake.value.style.left = cakeLocation[1] + 'px'
})
function getCode() {
  console.log(input.value);
  let strArr = input.value.split(' ')
  if (strArr[0].indexOf('/') === 0) {
    if (strArr.indexOf('/take') === 0) {
      if(dig()){
        outputText('挖到月饼啦~')
        gameFinish.value = true
      }else{
        console.log(coordinate,cakeLocation);
        outputText('没有挖到月饼')
        outputText(`我感觉距离月饼还有${calculation(coordinate,cakeLocation)}个单位`)
      }
    } else {
      move(strArr)
    }
  } else {
    outputText('无法理解的语句:(')
  }
  inputText(input.value)
  input.value = ''
  nextTick(()=>{
    textArea.value.scrollTop = textArea.value.scrollHeight
  })
}
function dig() {
  console.log(coordinate,cakeLocation);
  for (let i = 0; i < coordinate.length; i++) {
    if(Math.abs(coordinate[i] * 10 - cakeLocation[i]) > 10){
      return false
    }else{
      return true
    }
  }
}
function calculation(player,cake){
  let lineX = player[0]*10 - cake[0]
  let lineY = player[1]*10 - cake[1]
  return Math.floor(Math.sqrt(Math.floor(lineX*lineX + lineY*lineY))) / 10
}
  • 游戏开始时需要让月饼随机放在一个位置,需要在组件挂载成功后改变其位置,因此在onMounted钩子中操作使用ref取到的DOMcake
  • 一切输入都将进入getCode函数中,指令将在这里校验基本语法与对语句的处理
  • outputText是一个自己封装的一个用来输出的函数,将会在下面展示讲解
  • inputText是一个自己封装的用来操作input框的函数,同样将会在下面展示讲解
  • 值得注意的是在nextTick中放的是用来控制历史内容的scrollBar的,使其在发生输入事件时置于最下方,类似于聊天软件界面
  • dig是挖掘函数,如果月饼的位置在挖掘范围内的话则游戏成功,否则探测月饼距离
  • 如果指令为take,则开始挖掘,若不是,则将会进入到下一模块“移动”
  • coordinate是兔子当前位置的坐标

兔子移动

let player = ref(null)
const num = /^[0-9]+$/
function move(strArr) {
  let direction = strArr[0]
  console.log(strArr[1]);
  console.log(num.test(strArr[1]));
  if (num.test(strArr[1]) == true) {
    if (direction === '/up') {
      console.log(player.value.style, coordinate[0] + strArr[1] * 10);
      player.value.style.top = coordinate[0] * 10 - strArr[1] * 10 + 'px'
      coordinate[0] -= Number(strArr[1])
      outputText(`已向上移动${strArr[1]}个单位`)
    } else if (direction === '/right') {
      player.value.style.left = coordinate[1] * 10 + strArr[1] * 10 + 'px'
      coordinate[1] += Number(strArr[1])
      outputText(`已向右移动${strArr[1]}个单位`)
    } else if (direction === '/down') {
      player.value.style.top = coordinate[0] * 10 + strArr[1] * 10 + 'px'
      coordinate[0] += Number(strArr[1])
      outputText(`已向下移动${strArr[1]}个单位`)
    } else if (direction === '/left') {
      player.value.style.left = coordinate[1] * 10 - strArr[1] * 10 + 'px'
      coordinate[1] -= Number(strArr[1])
      outputText(`已向左移动${strArr[1]}个单位`)
    } else {
      outputText('无法理解的语句:(')
    }
  } else {
    outputText('无法理解的语句:(')
  }
  console.log(coordinate);
  for (let i = 0; i < coordinate.length; i++) {
    if (coordinate[i] < 0) {
      coordinate[i] = 0
      if (i == 0) {
        player.value.style.top = 0
      } else if (i == 1) {
        player.value.style.left = 0
      }
      outputText('越界啦!嫦娥姐姐说月饼就在这一片地方')
    } else if (coordinate[i] > 28) {
      coordinate[i] = 28
      if (i == 0) {
        player.value.style.top = 280 + 'px'
      } else if (i == 1) {
        player.value.style.left = 280 + 'px'
      }
      outputText('越界啦!嫦娥姐姐说月饼就在这一片地方')
    }
  }
}
  • num是一个正则表达式,表示只允许输入数字
  • direction提取出了输入指令的第一段,即移动方向
  • 在代码的第一行,使用了ref来获取到表示兔子的DOM

历史记录

let history = ref('')
function inputText(value) {
  history.value = history.value.concat(value, '<br>')
}
function outputText(value) {
  nextTick(()=>{
    history.value = history.value.concat('兔子:',value, '<br>')
  })
}
  • 这一片很简单,控制右侧输入输出容器的历史记录
  • history初始是一个空字符串,后续将会对其进行字符串的拼接
  • 每一次拼接都会在结尾加上<br>表示换行,注意只有在template中加上v-html才会对字符串进行HTML解析