为数字建立你自己的Wordlen Numble

172 阅读12分钟

为数字建立你自己的WordlenNumble

Wordle征服了世界和我的Twitter feed之后,我和世界上的其他人一样,变得有些痴迷。我是如此痴迷,以至于我产生了做一个应用程序的想法,类似的,但重点是数字。两周后,Numble诞生了--一个数字的Wordle。

Numble

Numble的规则

这真的很简单:猜测三位数的倍数。

你有四次猜测的机会,每次猜测后,每个数字的颜色都会改变,这取决于它的位置和它是否真的在Numble中:

  • 绿色:该数字在正确的位置。
  • 黄色:数字在Numble中,但是在错误的位置。
  • 灰色:这个数字根本不在Numble中。

如果你忘记了学校的规定,知道一个数字是否是3的倍数的规则是,如果数字加起来是3的倍数。

例如:123是3的倍数,因为1+2+3=6。

保姆状态

为了使构建Numble更容易,我使用了一个叫Nanny State的小库。它是由Darren Jones编写的,如果你错过了,他最近写了一篇文章介绍它。它将所有的应用程序数据存储在一个叫做State的对象中,然后根据State的任何变化,自动重新显示HTML视图。对于它的速度和效率,以及没有新的语法,它是非常简单和容易学习的。

首先,我们需要导入Nanny State并设置StateViewUpdate

要导入它,你只需要把这行代码复制到你程序的第一行。

import { Nanny,html } from 'https://cdn.skypack.dev/nanny-state'

如果你想了解更多关于你所导入的内容以及一切的结构是如何运作的,请务必查看上面的链接。接下来,我们需要创建视图。

const View = state => html`
<h1>Numble</h1>`

View 是一个函数,它返回一个字符串,基本上就是将在我们的页面上显示的HTML。这是一个让我们开始的基本布局,一旦一切就绪,应该会出现一个 "Numble "的标题。它把State 作为一个参数,让我们能够访问存储在单个对象中的数据。

现在我们需要创建State对象,这是任何数据都将被存储的地方,但现在它唯一需要的属性是View ,把它们联系在一起。

const State = {
  View
}

最后,我们需要调用Nanny 函数。这将渲染初始视图。我们还将变量Update 赋给返回值。这将使我们能够在以后更新状态。

const Update = Nanny(State)

你的页面应该看起来像这样。

Initial Page

下面是代码的全部内容。

现在 "保姆国 "已经设置好了,我们可以开始制作游戏,到最后应该是一个功能齐全的 "猜数字 "游戏。

每一个终点都有一个起点

在我们开始之前有几件事,View 函数的一个好处是它使用模板字面,这意味着我们可以在HTML本身中添加逻辑。当你想根据状态包含不同的视图时,这就非常方便了。不幸的是,一个标准的if 语句是行不通的,相反,我们需要使用三元运算符。

如果你还不熟悉,三元运算符的工作方式与if else语句相同:condition ? what to do if true : what to do if false。简而言之,用if 换成? ,用else 换成:

例如,这是我前几天在Twitter上看到的一个好例子。

const toDo = amIHungry ? "🍰" : "😴"

这就相当于。

if (amIHungry){
    const toDo = "🍰"
}
else{
    const toDo = "😴"
}

三元运算符即将成为你最好的朋友,所以你了解它们的工作原理是相当重要的。

开始按钮是给游戏添加一些结构的好方法,特别是像Numble这样的游戏,因此为了做到这一点,我们需要给Statestartedstarted 的值需要是false ,因为我们希望用户首先看到的是菜单页(现在,它将由开始按钮和标题组成)。

State 现在看起来应该是这样的。

const State = {
    started: false,
    start, finish,
    View
}

View 是这里变化最大的地方,我们可以使用我们的第一个和主要的三元操作符。

注意,它包括两个变量,叫做startfinish 。这些是对我们即将要写的事件处理程序的引用。

在Nanny State中,任何事件处理程序都需要在State 对象中被引用。

我们基本上想要两个不同的视图:一个是用户已经开始的时候(换句话说,游戏本身),一个是用户还没有开始的时候(也许是菜单页面)。所以我们可以使用我们的布尔属性started 来做到这一点。

const View = state => html`
<h1>Numble</h1>
${state.started ?
 html`<button onclick=${state.finish}>END</button>`
:
 html`<button onclick=${state.start}>START</button>`
}`

正如你所看到的,标题在三元运算符之上--这意味着它将在程序的两个页面上保持可见。因此,对于三元运算符,你可能已经能够准确地看到它在做什么,但如果没有,不要担心,它非常简单。

它所遵循的逻辑与上面的蛋糕/饥饿的例子完全相同:它检查state.started 是否是true 。如果是,就会显示一个名为'结束'的按钮。如果不是,就会显示一个叫做 "开始 "的按钮来代替。

Start Button

End Button

按钮本身有内联事件监听器:"开始 "按钮有一个调用函数start ,"结束 "按钮有一个调用函数finish 。很明显,这些函数还不存在,这使我们进入下一个工作:创建事件处理程序。

我们先做start 这个函数。这很简单,因为我们需要做的就是将started 属性更新为true 。代码应该是这样的。

const start = event => Update({started: true})

这使用了我们之前写的Update 函数,并改变了State ,使started 的值现在是true 。当这种情况发生时,视图将被重新渲染,显示 "结束 "按钮,这是我们的三元操作的结果。

你可能想自己写一下finish 事件处理程序,因为它的工作原理与start 函数几乎相同,唯一的区别是Update 函数正在改变什么。

下面是finish 函数应该是什么样子的。

const finish = event => Update({started: false})

令人惊讶的是!你现在有了世界上最无聊的游戏!

说实在的,你现在可以试着按一下开始和结束的按钮,与你的游戏进行互动,即使这不是最吸引人的体验。

同样,这里有一个例子,说明代码应该是什么样子的。

第2步:生成一个随机数

我们现在可以用Numble最重要的一个方面使我们的游戏更刺激:数字本身。

这一步涉及到一些数学和逻辑问题,但一旦你想明白了,就不会太难。这个函数本身应该是这样的(下面我将解释是怎么回事)。

const generateNumber = () => (3*Math.ceil(Math.random()*299+34)).toString()

这是一个箭头函数,以字符串的形式返回三位数、三的倍数。

具体看一下Math.ceil(Math.random()*299+34) ,它用Math.random() 生成一个 1 到 299 之间的随机数,并用Math.ceil 将其四舍五入。34被加上,然后乘以3,确保该数字是102和999之间的3的倍数,即3的3位数的倍数,或 "numble"。

最后,整个过程被包裹在一个.toString() 函数中,把它变成一个字符串。将一个数字存储为一个字符串可能看起来很奇怪,但这将使我们在以后的游戏中更容易给每个数字着色。

我们的下一个挑战是在用户每次按 "开始 "时显示一个数字。

做到这一点的最好方法是给状态对象添加一个属性,叫做number 。然而,我们不需要在原来的State ,我们只需要在'开始'按钮被按下时做这个,所以在start 事件处理程序中。

这将改变我们的start 函数,看起来像这样。

const start = event => Update({
    started: true,
    number: generateNumber()
  })

新属性的值,number ,是我们刚刚创建的函数generateNumber() 的返回值:随机的三位数,三的倍数。

为了显示这个,我们需要在View ,特别是在HTML部分添加一行,当state.startedtrue ,使View 现在看起来像这样。

const View = state => html`
<h1>Numble</h1>
${state.started ?
 html`<div id="number">${state.number}</div>
 <button onclick=${state.finish}>END</button>`
:
 html`<button onclick=${state.start}>START</button>`
}`

我们在这里所做的就是添加一个<div> ,其中id"number" ,显示state.number ,这是随机生成的三位数,3的倍数。

如果你现在测试一下这段代码,你每次点击 "开始 "按钮都能看到一个不同的数字,如果你把这些数字加起来,你会发现这些数字是3的倍数!

用我的CodePen演示检查你的代码。

第三步:输入和键盘

现在开始变得有点棘手了,在我们开始之前,最好先检查一下你是否熟悉Array.map() 函数。像三元运算符一样,在这一步和下一篇文章中,它们将成为你最好的朋友,因为Numble需要在数组上进行大量的映射来处理状态中的数据。

如果你的Array.map() 技能有点生疏,或者你甚至没有听说过它,不要担心,它们很容易理解,你可以在这里阅读更多关于它们的信息。

这个步骤有三个主要部分。

  • 创建一个虚拟键盘
  • 显示用户的猜测
  • 检查用户的猜测是否正确

尽管它们都是相互依赖的,但如果你把一切都分成小块,就会更容易理解。

首先,我们需要在State ,添加我们要使用的函数和另外三个属性。

const State = {
  started: false,
  digits: Array(10).fill("grey"),
  guess: Array(3).fill(null),
  count: 0,
  start, finish, remove, check, appear,
  View
}

按照这个顺序操作,digits 的值现在是一个长度为10的数组,每个空格都填上了字符串 "grey"。这是因为我们将用它来记录游戏中每个数字应该是什么颜色,数组的索引将代表0-9的每个可能的数字。

guess 的初始值也是一个长度为3的数组,每个空格都用null 填充。

最后,count 被设置为0,这将被用来统计玩家猜中了多少个数字。

我们将映射digits 数组来创建我们的屏幕键盘,所以我们需要向View 添加一些东西。不过在这之前,我们需要去掉显示number<div> ,否则就会破坏游戏的全部意义了。

const View = state => html`
<h1>Numble</h1>
${state.started ?
 html`<div id="guesses">
${state.guess.map(number => html`<div>${number}</div>`)}
</div>
<div id="keyboard">
 ${state.digits.map((digit,index) => html`<button onclick=${appear(index)}>${index}</button>`)}
 <button onclick=${remove}>DELETE</button>
 <button onclick=${check}>ENTER</button>
</div>
<button onclick=${finish}>END</button>`
:
 html`<button onclick=${start}>START</button>`
}`

id 取代显示number<div> ,我们现在有两个<div>,一个是"guesses" ,一个是id ,一个是"keyboard"

在 "猜测 "<div> 中,我们有许多.map() 函数中的第一个,它映射了长度为3的数组,为数组中的每个项目创建一个单独的<div> ,显示该项目。这意味着在开始时,当数组中所有项目的值为null ,将有三个空位显示。

下面是一个例子,说明它应该是什么样子(用我的CSS)。

Numble 1 Grid

最终,当数组中每个项目的值发生变化时,所显示的内容也会随之改变。

在'键盘'<div> ,我们有三个东西。

${state.digits.map((digit,index) => html`<button onclick=${state.appear(index)}>${index}</button>`)}

这映射了长度为10的数组,为每个项目创建一个按钮,并显示每个项目的index 。换句话说,就是数字0到9。每个按钮也有一个内联事件监听器,调用事件处理程序appear ,并提供index 作为参数。然而,我们将在稍后全面探讨这个问题。

然后,我们有两个按钮,一个叫 "删除",另一个叫 "输入"。它们都有内联事件监听器,调用各自的事件处理程序removecheck 。同样,我们将在稍后全面探讨这些。

首先,这是一个关于你的键盘可能是什么样子的例子。

Numble Keyboard

看一下appear 事件处理程序,我们希望这个函数显示玩家点击到guess 第一个空格的数字。

const appear = guess => event => {
  Update(state => ({
    guess: state.guess.map((digit,index) => index === state.count ? guess : digit),
    count: state.count + 1
  }))
}

首先,这个事件处理程序与我们之前做的事件处理程序的唯一区别是,这个函数有一个额外的参数guess 。这就是作为参数提供的digits 数组的index 。换句话说,它是玩家点击的数字。

Update 函数看起来有点不同。这是因为它需要访问状态,所以它被提供了一个箭头函数,将旧的状态映射到新的状态(Nanny State称这些为'转化器函数')

就它实际更新的内容而言,guess 属性映射到原来的三个nulls的数组上,如果项目的index 等于count (猜测的位置),null 的值就被替换为guess (这将是用户点击的按钮的编号)。如果index 不等于count ,项目的值就保持原样:null

然后它将count 递增1,允许用户在第二个空格中输入他们的第二个猜测。

这就是用户点击了几个数字后的行的样子。

Numble Filled Grid

remove 事件处理程序(讽刺的是)几乎是相同的。

const remove = event => {
  Update(state => ({
    guess: state.guess.map((digit,index) => index === state.count - 1 ? null : digit),
    count: state.count - 1
  }))
}

按照appear 函数的逻辑,你应该能够弄清楚这里发生了什么,但如果不是的话也不用担心。它通过映射原始数组来更新guess ,如果index 等于之前的猜测数(即计数-1),它就用null 替换该项的值,有效地删除该猜测。

而这一次,它将count 递减1,允许用户继续进行猜测。

只剩下check 这个函数了。

输入 "按钮的事件处理程序被称为check ,我们希望它能(惊喜地)检查用户的猜测是否正确,但我们也希望它能重置猜测,这样用户就可以再试一次。

这个函数看起来像这样。

const check = event => {
  Update(state => {
    const numble = state.guess.join("") === state.number
    return {
      feedback: numble ? "NUMBLE!" : "Wrong!",
      guess: Array(3).fill(null),
      count: 0
    }
  })
}

像以前一样,Update 使用一个转化器函数,并把state 作为参数,让我们直接访问状态中保存的所有应用程序数据。然后,它创建了一个布尔常数,叫做numble 。它看起来不像,但state.guess.join("") === state.number 实际上是一个条件(检查用户的猜测是否等于我们生成的数字),如果满足这个条件,numble 的值将是true ,如果不是,它将是false

然后它返回状态的三个更新的属性。

  • feedback 取我们刚刚创建的布尔值,如果是 ,则将该值设置为字符串 "NUMBLE!",如果是 ,则将该值设置为字符串 "Wrong!"true false
  • guess 被改回为一个长度为3的数组,其中充满了 。这将有效地重置用户的猜测,允许他们再次猜测。null
  • count 也被重置为0,意味着程序可以像从头开始一样工作。

我们的最后一步是把一些HTML放在View ,以便显示反馈。

一个好的地方是把它放在猜测的下方和键盘的上方。因此,你最终的View 应该是这样的。

const View = state => html`
<h1>Numble</h1>
${state.started ?
 html`<div id="guesses">
${state.guess.map(number => html`<div>${number}</div>`)}
</div>
<p id="feedback">${state.feedback}</p>
<div id="keyboard">
${state.digits.map((digit,index) => html`<button onclick=${state.appear(index)}>${index}</button>`)}
  <button onclick=${state.remove}>DELETE</button>
  <button onclick=${state.check}>ENTER</button>
</div>
 <button onclick=${state.finish}>END</button>`
:
 html`<button onclick=${state.start}>START</button>`
}`

如果你愿意,你可以使用feedback ,在游戏开始时设置一个信息,例如在start 事件处理程序中,你可以添加带有字符串值的feedback 属性("猜3位数")。

const start = event => {
  Update({
    started: true,
    number: generateNumber(),
    feedback: "Guess 3 digits"
  })
}

就这样!你现在有了一个功能完备的猜数字游戏!

在你继续阅读第二篇文章之前,只有几个关于CSS和bug的说明。

如果你想添加自己的CSS,那是完全可以的,但如果你只想关注代码,你可以从最后的CodePen演示中复制我的CSS。

如果你是一个好的程序员,你可能会发现这里的一些bug,例如,如果用户在猜到三个数字之前就点击'回车'怎么办?如果你开始玩它,你肯定能注意到更多的问题。

它们根本不难解决,你只需要在适当的地方设置几个条件。例如,为了解决在有三位数之前进行检查的问题,在check 函数中,你可以写。

const check = event => {
  Update(state => {
    const numble = state.guess.join("") === state.number
    return state.count < 3 ? {
      feedback: "too short"
    }
    :
    {
      feedback: numble ? "NUMBLE!" : "Wrong!",
      guess: Array(3).fill(null),
      count: 0
    } 
  })
}

这只是检查猜测的数字是否小于3,并相应地返回不同的属性值。

我们现在有了一个功能齐全的'猜数字'游戏,接下来我们将使它更像完整的Numble。

四个猜测

我们的第一项工作是允许用户进行4次猜测。在Wordle中,一个5个字母的单词允许有6个猜测,所以在Numble中,我们将允许4个猜测一个3位数的数字。

为了做到这一点,我们将不得不删除guess 属性,并在State 对象中增加两个属性。

const State = {
  started: false,
  digits: Array(10).fill("grey"),
  guesses: Array(4).fill(Array(3).fill(null)),
  guessCount: 0,
  count: 0,
  start, finish, check, appear, remove,
  View
}

正如你所看到的,我们现在有一个guesses 属性来代替我们之前的guessguesses 的值是一个二维数组,由4个数组组成,每个数组的长度为3,并填充有null 。如果你不熟悉Array.fill() 函数,它是创建数组的一个捷径,意味着我们不必完整地写出数组。

4个嵌套数组中的每一个都代表了用户将进行的4个猜测中的一个。例如,如果第一个猜测是123,guesses 数组会是这样的。

[[1,2,3], [null, null, null], [null, null, null], [null, null, null]]

每次用户做出猜测,这个数组都会被更新,以匹配他们的猜测,有效地记录了他们在游戏中做出的所有猜测。

此外,我们还有一个guessCount 属性,设置为0。虽然与count 属性类似,但它将允许我们记录用户所做的猜测的数量。

这张图应该可以帮助你直观地了解并充分理解对countguessCount 两个属性的需求。

Annotated Numble Grid

正如你所看到的,guessCount 是猜测存储在哪个嵌套数组中的索引,count 是每个猜测的每个数字的索引。

现在我们需要对View 函数做一些修改。

const View = state => html`
<h1>Numble</h1>
${state.started ?
 html`<div id="guesses">
${state.guesses.map((guess, i) => html`<div class="row">${guess.map((number,j)=> html`<div class="grey">${number}</div>`)}</div>`)}
</div>
<p id="feedback">${state.feedback}</p>
<div id="keyboard">
${state.digits.map((digit,index) => html`<button onclick=${state.appear(index)}>${index}</button>`)}
  <button onclick=${state.remove}>DELETE</button>
  <button onclick=${state.check}>ENTER</button>
</div>
 <button onclick=${state.finish}>END</button>`
:
 html`<button onclick=${state.start}>START</button>`
}`

这与我们之前创建的View 几乎相同,但是id为 "guesses "的div已经改变。事实上,我们现在使用一个2D-array来显示4个猜测,就像一个网格一样,我们将需要一个嵌套地图。

编码提示:当使用嵌套地图时,对于每个地图的索引,我们将对第一个地图使用i ,对第二个地图使用j 。你可以使用任何你认为最简单的方法,只要它们不一样就可以了。

第一张地图将每个猜测作为网格的一行进行循环。第二张地图则循环显示该猜测的每个数字,并显示相关的HTML,以显示被猜中的数字或一个空圈。有了这个,你的屏幕应该看起来像这样。

Numble Grid

这种新的布局意味着我们还必须改变appearremove 的功能。这相对简单,但又需要一个双图。

const appear = guess => event => {
  Update(state => ({
    guesses:  state.guesses.map((array,i) => i === state.guessCount ? array.map((digit,j) => j === state.count ? guess : digit) : array) ,
  count: state.count + 1 
  }))
}

我们在这里更新guesses 属性,这时有两个不同的count 属性就会变得非常有用。

第一个映射检查要改变的行:如果数组的索引与用户的猜测相符,那么第二个映射就可以发生,否则就保持数值不变。

第二张地图执行的逻辑与我们在第二条中创建的appear 完全相同。

就像以前一样,remove 函数的工作原理几乎相同。

const remove = event => {
  Update(state => ({
    guesses: state.guesses.map((array,i) => i === state.guessCount ? array.map((digit,j)=> j === state.count - 1 ? null : digit) : array),
    count: state.count - 1
  }))
}

这里的第一张地图同样只是识别用户正在进行的猜测,第二张地图遵循的逻辑与我们原来的remove 函数相同。

不过,count 属性会递减,以确保用户可以重新进行猜测。

最后,我们需要对check 函数做一些修改。这是一个在用户每次提交猜测时都会运行的函数。

const check = event => {
  Update(state => {
    const numble = state.guesses[state.guessCount].join("") === state.number
    return {
      feedback: numble ? "NUMBLE!" : state.guessCount < 3 ? "Keep going..." : `Nope! It was ${state.number}`,
      guessCount: state.guessCount + 1,
      count: 0
    }
  })
}

这里只有两件事发生了变化,而且都是在返回的对象中。feedback 属性增加了一些逻辑,使应用程序更加动态。现在反馈将显示一条信息,让用户知道他们的进展情况。

在这种情况下,我们有:如果numbletrue ,换句话说,如果用户的猜测是正确的,反馈会变成 "NUMBLE";如果numblefalse ,检查猜测是否小于3(这基本上是检查用户是否已经做出了他们的最终猜测)。如果是,反馈是 "继续......",否则是 "不!那是(答案)"。

第一部分就这样了!你可以在下面的CodePen演示中看到完整的代码。

颜色逻辑

正如文章开头所述,颜色是Wordle的重点,也是Numble的重点。如果你还没有玩过NumbleWordle,我强烈建议你玩一下,以便正确理解颜色的工作方式。

这就是Numble使用的着色系统的例子。

Numble Color Example

在用户进行猜测后,颜色会在两个地方更新:实际的猜测和键盘上的颜色。两者的逻辑是完全一样的,所以我们可以创建一个箭头函数,叫做getColors ,它把猜测和实际数字作为参数。

const getColors = (guess,number) => guess.map((digit,index) => number.includes(digit) ? digit.toString() === number[index] ? "green" : "yellow": "black")

我们映射到'猜测'数组上,使用'String.includes(item)'方法,首先检查答案是否包括猜测的数字。如果有,我们就检查这个数字是否在正确的位置。如果是的话,颜色就被定为 "绿色"。如果不是,则颜色为 "黄色"。否则,该数字根本不在答案中,所以颜色为 "黑色"。

这个箭头函数应该返回一个数组,其中有三个项目是 "绿色"、"黄色 "或 "黑色",对应于 "猜测 "中的每个数字。

例如,如果我们使用getColors([1,2,3], "327") ,那么我们应该返回的数组是["black", "green", "yellow"]

你可能注意到我们不得不将数字改为字符串。这是因为我们需要将其与存储为字符串的答案进行比较,而如果两个元素的类型不同,则无法进行比较。好吧,你可以试试,但要准备好进入一个完整的JavaScript类型强制的痛苦世界。

注意:Wordle对重复的处理方式不同,所以如果你想让这个方法更难一点,你可以尝试模仿Wordle的方法。

对于下一部分,我们不需要对State ,也不需要添加任何东西,但是View 确实变得有点复杂。正如在第一篇文章中简单提到的,我们将使用CSS类来允许我们改变颜色。

const View = state => html`
<h1>Numble</h1>
${state.started ?
 html`<div id="guesses">
${state.guesses.map((guess, i) => html`<div class="row">${guess.map((number,j)=> html`<div class=${state.guessCount > i ? getColors(guess,state.number)[j] : "grey"}">${number}</div>`)}</div>`)}
</div>
<p id="feedback">${state.feedback}</p>
<div id="keyboard">
${state.digits.map((digit,index) => html`<button class=${digit} onclick=${state.appear(index)}>${index}</button>`)}
  <button onclick=${state.remove}>DELETE</button>
  <button onclick=${state.check}>ENTER</button>
</div>
 <button onclick=${state.finish}>END</button>`
:
 html`<button onclick=${state.start}>START</button>`
}`

正如你所看到的,唯一改变的两件事是键盘按钮和每一行的各个部分的CSS类。

从 "猜测 "div开始,我们有以下逻辑。

state.guessCount > i ? getColors(guess,state.number)[j] : "grey"

首先,这将检查guessCount 是否高于索引,这是为了确保每次重新渲染页面时,任何先前的猜测都会重新着色。如果需要上色,我们以用户的猜测和答案为参数调用getColors 函数,并取每个数字的索引处的项目,j

下面是用户猜测一次后,你的屏幕应该是这样的。

First Guess

来自getColors 函数的数组是。

["yellow", "black", "black"]

因此,用户现在会知道3在这个数字中,但位置不对,4和5根本不在这个数字中。

键盘的逻辑要简单得多,但它仍然使用我们之前写的那个getColor 函数。还记得之前我们是如何用 "灰色 "填充digits 数组的吗?这就是我们这样做的原因。

当键盘在屏幕上被画出来的时候,这个类只是键的索引在digits 数组中的值。稍后我们将运行如何改变颜色,但使用上面的例子,在第一次猜测之后,digits 数组应该看起来像这样。

["grey", "grey", "grey", "yellow", "black", "black", "grey", "grey", "grey", "grey"]

我们就快成功了!我们的最后一项工作是改变check 函数。

const check = event => {
  Update(state => {
    const guess = state.guesses[state.guessCount]
    const numble = guess.join`` === state.number
    const colors = getColors(guess,state.number)
    return {
      feedback: numble ? "NUMBLE!" : state.guessCount < 3 ? "Keep going..." : `Nope! It was ${state.number}`,
      digits: state.digits.map((colour,digit) => guess.includes(digit) ? colors[guess.indexOf(digit)] : colour),
      guessCount: state.guessCount + 1,
      count: 0
    }
  })
}

Update 函数中,还有两个常数。这只是为了方便返回对象中的逻辑。

我们有guess ,这是用户刚刚猜到的三个数字的数组(因此使用了state.guessCount )。我们还有之前的numble ,但这次使用了我们刚刚创建的guess 常量。这样做只是为了让代码更简洁,避免重复。最后,我们有colors ,这是运行getColors 函数时返回的数组,其中有用户当前的猜测和答案。

这将更新数字数组,并确保每次猜测后键盘上的数字都能正确着色。

现在,返回对象与上面的对象相同,但我们也在更新digits 属性。

state.digits.map((color,digit) => guess.includes(digit) ? colors[guess.indexOf(digit)] : color)

这是我们最后的映射函数!它主要是检查键盘上的数字(也就是digit )是否在猜测中。如果是的话,当前的颜色应该被从getColors 函数生成的颜色所取代,否则颜色应该保持不变。

使用与上述相同的猜测,我们可以看到键盘应该是什么样子。

First Guess Keyboard

这就是了!一个功能齐全的Numble版本!

同样的,这里是代码的全部样子。

在Numble的实际版本中,我增加了一些功能,只是为了使游戏更加有活力。如果你想挑战自己并增加一些额外的功能,这里有一些来自我的Numble最终版本的建议。

  • 再玩一次--允许用户想玩多少次就玩多少次,或者让它每天只有一次挑战机会

  • 连胜--记录你连续的正确答案的数量

  • 最佳连胜--用户保持的最长的连胜纪录

  • 黑暗模式--更像是一个CSS挑战,但也很有趣

  • 分享功能--让用户分享他们的最佳连胜记录

我真的希望你在制作Numble的过程中和我一样有乐趣!