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

前言

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

原版示例:

复刻版:

欢迎各位来玩:

游戏地址 源码

游戏介绍

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

是不是感觉很有意思呢😎

需求分析

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

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

我设计了两种游戏模式

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

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

项目搭建

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,谢谢!

相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic8 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy8 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜11 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司11 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g12 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森12 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http