摸鱼时写的小游戏,看看你能玩到第几关?

前言

刷视频时在某平台看到的的一款小游戏,有点想玩但又不想买,所以发动了传统艺能 ------ 仿写一个。

原版示例:

复刻版:

欢迎各位来玩:

游戏地址 源码

游戏介绍

问题区通过滚动的方式不停地展示题目,等滚动完一定数量的题目后,解锁答题区,开始依次填写答案,在回答完一题后问题区继续向后滚动。

是不是感觉很有意思呢😎

需求分析

首先,这是个非常小的纯前端项目,实现主要功能即可。

因为没有玩过原版的游戏,所以只能靠自己设计需求。

我设计了两种游戏模式

  • 闯关模式:生成一定数量的题目,设置闯关要求的正确率。用户需要回答完所有题目,最后计算正确率是否达标。
  • 无尽模式:不断地生成题目,设置最大错误量。用户可以不停地不停地答题,直到错误数量超出要求,游戏结束,最后计算用户得分。每题一分。

此外还需要要求游戏中用的参数都可自定义,所以需要做一个自定义的页面,根据需要自定义游戏难度。

项目搭建

Vite + Vue3 + TS + Tailwind 一梭子走起。

使用 vitesse-lite 直接生成项目目录,将目录简单改造一下,替换一下 unocss -> Tailwind(这纯粹属于个人习惯)。

由于不需要后台交互,也没打算做的太过复杂,所以一切从简。

界面设计

对于一个小前端来说,审美问题真的是一个很大的挑战,不过好在项目并不复杂,只需要设计三个页面。

虽然要简单设计,但也不能太过潦草,选择了一些渐变色做背景,从大佬的这个网站COPY了一些动画效果。整个界面还算说得过去。

至于那些奇奇怪怪的配色则是我随便选的😜

开始开发

1. 生成题目

首先制定好生成一组题目所需要用到的参数,运算规则使用最为简单的加减乘除,其余参数则是根据不同模式的需要来制定。

ts 复制代码
/**
 * 运算规则
 */
export type IMethods = '+' | '-' | '*' | '/'

/**
 * 题目生成的条件
 *
 * type: => 'normal' 闯关模式, 'endless' 无尽模式, 'diy' 自定义
 */
export interface INormalOptions {
  type: 'normal'
  level: number
  range: number // 取值范围
  preNum: number // 前置题目数量
  methods: IMethods[]
  accuracy: number // 正确率 取百分比
  questionNum: number // 题目数量
}

export interface IEndlessOptions {
  type: 'endless'
  errNumber: number // 错误数量
  preNum: number
  methods: IMethods[]
}

export interface IDiyOptions {
  type: 'diy'
  methods: IMethods[]
  range: number
  preNum: number
  successType: 'normal' | 'endless' // 按照闯关模式的通关条件, 按照无尽模式的通关条件
  accuracy?: number // 准确率
  questionNum?: number // 题目数量
  errNumber?: number // 错误数量
}

export type ICreateQuestionOptions = INormalOptions | IEndlessOptions | IDiyOptions

在游戏界面,根据路由携带的参数调用generate来生成一定数量的题目,将题目追加到题目数组中。根据题目数量进行循环,每次都通过随机的方式选取运算规则。

ts 复制代码
/**
   * 创建 num 数量的题目
   * @param num
   */
  const generate = (options: ICreateQuestionOptions, num: number) => {
    const _questionList = []
    const baseIndex = questionList.value.length

    for (let index = 0; index < num; index++) {
      const fn = getMethod(options.methods)
      let _range = 0
      /**
       * 如果有预设的范围,按照预设
       */
      if ('range' in options) {
        _range = options.range
      } else {
        /**
         * 没有预设范围,'endless'模式 | 'diy'模式-'endless'条件
         *
         * +、- 为20
         * *、/ 为15
         */
        if (fn === '+' || fn === '-') {
          _range = 20
        } else {
          _range = 15
        }
      }

      const question = createQuestion[fn](_range, baseIndex + index)

      _questionList.push(question)
    }

    questionList.value.push(..._questionList)

    return _questionList
  }

对于不同的运算方式要做出不同的处理来保证题目的难度。

  • 加法:从运算范围内随便算两个数字相加就行
  • 减法:与加法同理,但要保证答案是个正数或0
  • 乘法:乘除都不受运算范围的控制,因为太大的数也不好算(至少我没那个脑子去算),这里最大只取到9
  • 除法:同样是为了保证题目的运算方便,显示随机取两个数进行乘法运算,将结果与其中一个数字作为问题,另一个数字作为答案。
ts 复制代码
/**
 * 创建题目
 */
export const createQuestion: Record<IMethods, (range: number, i: number) => IQuestion> = {
  '+': (range, i) => {
    const a = getRoundNum(range - 1)
    const b = getRoundNum(range - a)

    const answer = a + b

    return { a, b, fn: '+', answer, i }
  },
  '-': (range, i) => {
    let a = getRoundNum(range)
    let b = getRoundNum(range)

    /**
     * 如果a小于b,交换位置
     * 保证答案为正数
     */
    if (a < b) {
      [a, b] = [b, a]
    }

    const answer = a - b

    return { a, b, fn: '-', answer, i }
  },
  /**
   * 不受运算范围的限制
   * @param _
   * @param i
   */
  '*': (_, i) => {
    const a = getRoundNum(9)
    const b = getRoundNum(9)
    const answer = a * b

    return { a, b, fn: '*', answer, i }
  },
  '/': (_, i) => {
    const b = getRoundNum(9)
    const answer = getRoundNum(9)
    const a = answer * b

    return { a, b, fn: '/', answer, i }
  },
}

2. 用户输入

使用系统自带的键盘来玩游戏体验十分糟糕,所以在屏幕上做了一个九宫格的小键盘,只需要 0-9 以及 删除提交

使用Grid画个键盘还是很简单的。之后是绑定键盘事件、点击事件,这里计划做到PC端与移动端通用,所以对两端的监听事件分别做了处理。并在最后都汇总到统一的提交事件中。

ts 复制代码
/**
 * 用户的答案成绩记录
 */
export const answerRecord = ref<boolean[]>([])

 /**
* 当前的答案
*/
const curAnswer = ref<string[]>([])
  
/**
* 按钮操作
* @param key
*/
const handleCurAnswer = async(key: string) => {
    /**
    * 最大输入为3位数
    */
    if (curAnswer.value.length > 2 && key !== 'Enter' && key !== 'Backspace')
      return

    if ((key === 'Enter' || key === 'Backspace') && curAnswer.value.length === 0) {
      return
    }

    /**
    * 提交当前答案
    */
    const submitCurAnswer = async() => {
       /**
       * 获取答题结果
       */
      const result = await getSubmitResult(Number(showCurAnswer.value), answerIndex.value)

      answerRecord.value.push(result)
      answerIndex.value += 1
      curAnswer.value = []

      nextTick(() => {
        submitEnd(answerRecord.value)
      })
    }

    switch (key) {
      case 'Backspace':
        curAnswer.value.pop()
        break
      case 'Enter':
        submitCurAnswer()
        break
      default:
        curAnswer.value.push(key)
        break
    }
|

3. 游戏结束

因为两种游戏模式的结束条件不同,所以每次提交当前答案时都要判断游戏是否结束

ts 复制代码
/**
* 判断游戏是否结束
*/
const isGameOver = (list: boolean[]) => {
   if (scoreType.value === 'percentage') {
     if (list.length === allQuestionLength.value) {
       return true
     }
   } else {
     if (errNumber.value > (playOptions.value as IEndlessOptions).errNumber) {
       return true
     }
   }

   return false
}

在游戏确认结束后,计算出分数与结果。

ts 复制代码
   /**
   * 计算得分
   */
   const computedScore = (list: boolean[]) => {
    const trueNum = list.filter(Boolean).length
    const allNum = list.length

    const options: IResultOptions = {
      type: scoreType.value,
      num: 0,
      result: false,
    }

    if (scoreType.value === 'percentage') {
      /**
     * 闯关模式
     * 计算分数,是否通过
     */
      const num = Math.floor(trueNum / allNum * 100)

      options.result = num >= (playOptions.value as INormalOptions).accuracy
      options.num = num
    } else {
      /**
       * 无尽模式
       * 给出分数即可
       */
      options.num = trueNum
    }

    return options
  }

结语

OK,以上就是我在公司摸鱼时写的小游戏,虽然项目很小,但玩的时候也挺有意思。欢迎各位来体验呀。

游戏地址

源码

最后不要脸的求个star,谢谢!

相关推荐
最逗前端小白鼠13 分钟前
vue3 数据响应式遇到的问题
前端·vue.js
倚栏听风雨37 分钟前
ts中 ?? 和 || 区别
前端
冴羽41 分钟前
请愿书:Node.js 核心代码不应该包含 AI 代码!
前端·javascript·node.js
我家猫叫佩奇43 分钟前
一款灵感源自《集合啦!动物森友会》的 UI 组件库
前端
mmmmm123421 小时前
深入 DOM 查询底层:HTMLCollection 动态原理与 querySelectorAll 静态快照解析
前端·javascript
weixin199701080161 小时前
《TikTok 商品详情页前端性能优化实战》
前端·性能优化
卤蛋fg61 小时前
vxe-table 自定义数字行主键,解决默认字符串主键与后端类型不匹配问题
vue.js
闲坐含香咀翠1 小时前
告别二次登录!Web端检测并唤起Electron客户端实战
前端·客户端
岁月宁静1 小时前
都知道AI大模型能生成文本内容,那你知道大模型是怎样生成文本的吗?
前端·vue.js·人工智能
花间相见2 小时前
【终端效率工具01】—— Yazi:Rust 编写的现代化终端文件管理器,告别繁琐操作
前端·ide·git·rust·极限编程