javascript - 跟踪多个文本输入的值以确认它们在 JavaScript/React 文字游戏中都是正确的最佳方法

标签 javascript reactjs algorithm user-input state-management

我正在为我的投资组合制作一个密码文字游戏(破译名言)。我遇到了最后一个主要障碍:确认难题已解决。

我没有声望点来发布真正有助于澄清的图片,但想想财富之轮,其中每个白框都是一个单独的输入。我没有将它们附加到任何状态,因为每个谜题很容易有超过 100 个输入。我一直在通过 className 操纵它们以相应地自动填充输入(即,如果玩家在一个输入中输入“e”,所有类似的输入都将填充为“e”)

我以为我已经确定了完成确认,但很快意识到我的解决方案有多么缺陷。我正在创建一个 pendingSolution 状态,它是一个字符串,并试图通过输入中的 OnChange 函数适本地填充它。每次值更改时我都会检查它是否与解决方案字符串匹配,但事实证明我没有正确设置 pendingSolution 字符串的状态来更新玩家在谜题中的进度。我不确定这是否是最好的方法,所以我正在寻求一些帮助来修复我的 updatePendingSolution 函数,或者寻求有关解决此问题的更好方法的建议。

例子:

  • “*h*** *o* *o* *o** h***!” <-- 悬而未决的解决方案
  • “谢谢你的帮助!” <-- 解决方案

这些函数用于处理输入的 OnChange 以及自动填充最多三个随机字母作为 onClick 提示。

const handleChange = (e) => {
     let letter = document.getElementsByClassName(e.target.className)
     for (let i=0; i < letter.length; i++) {
          letter[i].value = e.target.value
          letter[i].style.color = "darkblue"
     }
     setPendingSolution(pendingSolution.split('').map((char, i) => char.replace(/\*/, updatePendingSolution(char, e.target.value, i))).join(''))    
}

const handleClick = () => {
     const rand = Math.floor(Math.random() * 26)
     if(solution === pendingSolution) return
     if (randLetters.includes(rand)) {
          handleClick()
     } else {
          setRandLetters([...randLetters, rand])
          let letter = document.getElementsByClassName(`char_input ${shuffledAlphabet[rand].toLowerCase()}`)
          if (letter.length === 0) {
               letter = document.getElementsByClassName(`char_input ${shuffledAlphabet[rand]}`)
               if(letter.length === 0) handleClick()    
          } else {
               if (letter[0].value.toUpperCase() === alphabet[rand]) {
                    handleClick()
               } else {
                    for (let i=0; i < letter.length; i++) {
                         letter[i].value = alphabet[rand]
                         letter[i].disabled = true
                         letter[i].style.color = 'green';
                         setPendingSolution(pendingSolution.split('').map((char, i) => char.replace('*', updatePendingSolution(char, alphabet[rand], i))).join(''))
                    }
               }
          }
          setHints([...hints, alphabet[rand]])               
     }      
}

这是我需要帮助的部分。这是我正在研究的函数,每当玩家使用提示或更改输入时更新 pendingSolution 状态。

useEffect(() => {
        if (solution && pendingSolution) {
            if (solution === pendingSolution) {
                alert("You won!!!")
            }
        }
    }, [pendingSolution])

const updatePendingSolution = (char, value, i) => {
     if (solution[i].match(value.toUpperCase())) {
          return value.toUpperCase()
     } else if (solution[i].match(value.toLowerCase())) {
          return value.toLowerCase() 
     } else return char   
}

这是呈现引号和名称组件的代码:

function EncryptedText({ divName, onChange, words } ) {
    return (
        <div className={divName}>{words.split(" ").map((word, i) => {
            return (
                <div className="word_div" key={i}> {word.split("").map((char, j) => {
                    return (
                        <div key={j} className="char_container">
                            {char.match(/[A-Za-z]/) ?
                                char.match(/[A-Z]/) ?
                                    <div>
                                        <p className="puzzle_p uppercase"><input className={`char_input ${char.toLowerCase()}`} maxLength="1" onChange={onChange} type="text" /></p>
                                    </div>
                                    :<div>
                                        <p className="puzzle_p lowercase"><input className={`char_input ${char}`} maxLength="1" onChange={onChange} type="text" /></p>
                                    </div>
                                : <p className="puzzle_p">{char}</p>
                            }
                            <p className="puzzle_p">{char}</p>
                        </div>
                    )
                })}</div>
            )
        })}</div>
    )
}

export default EncryptedText

最佳答案

虽然这是一种完全不同的方法,也没有通过 React 实现,但 OP 可能会从中得到一些启发。

主要思想是提供一种“命运之轮”组件。

这样的组件会处理所有用户交互的状态,并且它“知道”何时解决。

为此,它会清理传入的所有文本并将其拆分为单词。然后将每个单词拆分为字符,并为每个字符创建一个相应的 input 元素。单词是分组到列表中的字符,句子/引语/引文由此类列表组成。

简化任何其他任务的诀窍是引入...

  1. 基于弱引用的映射,其中每个输入元素都是其对应有效字母字符的键。
  2. 一个映射,其中对于每个有效的小写字母字符,都存储/聚合输入元素列表,每个元素都与该字符相关。
  3. 任何已创建输入元素的引用数组。

... 并且任何组件在内部都将以上三个作为引用。

然后只需要在组件根节点处理任何input 事件(事件委托(delegate))。

对于每个事件目标(input 元素),确实通过节点引用查找正确的字符。然后比较查找字符和节点值的小写值。如果它们相等,则有一个匹配项,并且可以继续通过小写字符查找其他匹配的输入元素。对于每个其他匹配元素,将自动填充该元素的相关正确字符大小写。

因为人们总是需要检查组件是否已解决。这是通过将所有当前元素值连接到一个字符串中,并将其与原始传递文本的净化版本进行比较来完成的。

下面提供的组件实现具有可由第三方代码使用的response promise。 promise 在内部通过上一段中描述的过程得到解决。

function isComponentSolved(match, placeholders) {
  const currentValue = placeholders
    .reduce((value, node) => value + node.value, '');

  return (match === currentValue);
}

function handleCharacterMatchUI(node, className) {
  const itemNode = node.closest('li');
  const { classList } = itemNode;

  classList.add(className);
  setTimeout((() => classList.remove(className)), 300);
}
function handleNextPlaceholderFocus(target, placeholders) {
  let idx = placeholders
    .findIndex(node => node === target);

  let nextNode = placeholders[++idx];

  while (nextNode) {
    if (nextNode.disabled) {

      nextNode = placeholders[++idx];
    } else {
      nextNode.focus();
      nextNode = null;
    }
  }
  if (idx >= placeholders.length) {
    idx = 0;
    nextNode = placeholders[idx];

    while (nextNode !== target) {
      if (nextNode.disabled) {

        nextNode = placeholders[++idx];
      } else {
        nextNode.focus();
        nextNode = target;
      }
    }
  }
}

function handleInputFromBoundComponentData({ target }) {
  const {
    settle,
    match,
    placeholders,
    charByNodeMap,
    nodesByCharMap,
  } = this;

  const value = target.value.toLowerCase();
  const char = charByNodeMap.get(target).toLowerCase();

  if (value === char) {
    nodesByCharMap
      .get(char)
      .forEach(node => {

        node.disabled = true;
        node.value = charByNodeMap.get(node);

        handleCharacterMatchUI(node, 'match');
      });
      handleNextPlaceholderFocus(target, placeholders);
  } else {
    handleCharacterMatchUI(target, 'mismatch');
  }

  if (isComponentSolved(match, placeholders)) {
    // resolve the component's
    // `response` promise with
    // the 'solved' payload.
    settle('solved');
  }
}

function createPlaceholderNode(char, charByNodeMap, nodesByCharMap) {
  const node = document.createElement('input');

  // test for Letter only character
  // using unicode property escapes.
  if ((/\p{L}/u).test(char)) {
    let nodeList = nodesByCharMap.get(char);

    if (!nodeList) {
      nodeList = [];
      nodesByCharMap.set(char.toLowerCase(), nodeList);
    }
    nodeList.push(node);

    charByNodeMap.set(node, char);
  } else {
    // non Letter character.
    node.disabled = true;
    node.value = char;
  }
  node.type = 'text';

  return node;
}

function createWheelOfFortuneComponent(root) {
  let response = null; // a promise for any valid component.

  const text = (root.dataset.wheelOfFortune ?? '')
    .trim().replace((/\s+/g), ' '); // text normalization.

  if (text !== '') {
    const charByNodeMap = new WeakMap;
    const nodesByCharMap = new Map;

    const placeholders = [];
    const wordRootList = text
      .split(/\s/)

      .map(word => word
        .split('')

        .reduce((listNode, char) => {
          const itemNode = document.createElement('li');
          const phNode =
            createPlaceholderNode(char, charByNodeMap, nodesByCharMap);

          placeholders.push(phNode);

          itemNode.appendChild(phNode);
          listNode.appendChild(itemNode);

          return listNode;
        }, document.createElement('ol'))
      );

    let settle;
    response = new Promise(resolve => {
      settle = resolve;
    });

    root.addEventListener(
      'input',
      handleInputFromBoundComponentData.bind({
        settle,
        match: text.replace((/\s/g), ''),
        placeholders,
        charByNodeMap,
        nodesByCharMap,
      })
    );
    wordRootList
      .forEach(wordRoot => root.appendChild(wordRoot));
  }
  root.dataset.wheelOfFortune = '';

  return {
    root,
    response,
  };
}

async function handleComponentResponseAsync({ root, response }) {
  if (response !== null) {
    const result = await response;

    root.classList.add(result);
  }
}
function app() {
  [...document.querySelectorAll('[data-wheel-of-fortune]')]
    .map(createWheelOfFortuneComponent)
    .forEach(handleComponentResponseAsync)
}
app();
[data-wheel-of-fortune] {
  margin: 8px 0;
  padding: 0 0 3px 0;
}
[data-wheel-of-fortune].solved {
  outline: 2px solid green;
}
[data-wheel-of-fortune].failed {
  outline: 2px solid red;
}
[data-wheel-of-fortune] ol,
[data-wheel-of-fortune] ul {
list-style-type: none;
  display: inline-block;
  margin: 0 4px;
  padding: 0;
  position: relative;
  top: 3px;
}
[data-wheel-of-fortune] ol::after,
[data-wheel-of-fortune] ul::after {
  clear: both;
  content: '';
}
[data-wheel-of-fortune] li {
  position: relative;
  float: left;
  margin: 0;
  padding: 0;
}
[data-wheel-of-fortune] li [type="text"] {
  width: 11px;
  margin: 0;
  padding: 0;
  text-align: center;
}
[data-wheel-of-fortune] li::after {
  z-index: 1;
  position: absolute;
  display: block;
  content: '';
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  transition-property: opacity;
  transition-duration: .3s;
  opacity: 0;
}
[data-wheel-of-fortune] li.match::after {
  background-color: #c2ef2f;
  opacity: .5;
}
[data-wheel-of-fortune] li.mismatch::after {
  background-color: #fb5100;
  opacity: .5;
}
<article data-wheel-of-fortune="The journey of a thousand miles begins with one step."></article>

<article data-wheel-of-fortune="Great minds discuss ideas; average minds discuss events; small minds discuss people."></article>

<article data-wheel-of-fortune="Thank you for your help!"></article>

关于javascript - 跟踪多个文本输入的值以确认它们在 JavaScript/React 文字游戏中都是正确的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71744854/

相关文章:

javascript - 当您导航回到它时更新已安装的屏幕

algorithm - 生成递增序列的数学公式

c# - 在圆上转录多边形

algorithm - 对矢量场进行颜色编码

javascript - 在 tbody 标签中应用某个类

javascript - 导入的 PropTypes 的 Intellisense

javascript - 在 map 中显示子模式的最佳方式?

javascript - 如何制作精美的卷轴

javascript - 使用 http header 预览 pdf

javascript - 检测网页是否在二维码阅读器应用程序中打开