集成麦克风的 Wordle 克隆:为什么 addWord() 不起作用,而 addKey() 按预期运行?

问题描述 投票:0回答:1

我有一个用 Expo React-Native w/ TypeScript 制作的 Wordle 克隆。我遵循了 youtube 上的教程Building a Wordle Game with React Native - Simon Grimm,它解释了游戏的基本逻辑,添加行和列,管理键盘按键等。所有这些都很有效,但我的教授想要我添加一个麦克风输入,以便可以听写单词,而不是打字。

经过一番修补后,我设法使麦克风完美工作;仅当单词是 5 个字母的单词时,它才会将其注册为字符串(因为游戏无论如何都只适用于这些类型的单词),并且该单词作为道具从

OnScreenKeyboard
组件传递到主
Game
组件,然后可以对其进行处理或添加到游戏的当前状态。

这里是

game.tsx
,它处理游戏逻辑,正如我所说,OnScreenKeyboard.tsx 中麦克风收到的单词进入
addWord()
函数,但不知何故,它无法尊重应插入单词的行,每次都会将 mic 收到的单词插入到第一行。

游戏.tsx

const [rows, setRows] = useState<string[][]>(new Array(ROWS).fill(new Array(5).fill('')));
const [curRow, setCurRow] = useState(0);
const [curCol, _setCurCol] = useState(0);

const [blueLetters, setBlueLetters] = useState<string[]>([]);
const [yellowLetters, setYellowLetters] = useState<string[]>([]);
const [grayLetters, setGrayLetters] = useState<string[]>([]);

// Random word gets generated
const [word, setWord] = useState<string>(words[Math.floor(Math.random() * words.length)]);

const wordLetters = word.split('');

const colStateRef = useRef(curCol);
const setCurCol = (col: number) => {
  colStateRef.current = col;
  _setCurCol(col);
};

// Checks the word, does the flip animation, paints the tiles and keyboard
const checkWord = () => {
  const currentWord = rows[curRow].join('');
  if (currentWord.length < word.length) {
    shakeRow();
    return;
  }
  if (!allWords.includes(currentWord)) {
    shakeRow();
    return;
  }

  flipRow();

  const newBlue: string[] = [];
  const newYellow: string[] = [];
  const newGray: string[] = [];

  currentWord.split('').forEach((letter, index) => {
    if (letter === wordLetters[index]) {
      newBlue.push(letter);
    } else if (wordLetters.includes(letter)) {
      newYellow.push(letter);
    } else {
      newGray.push(letter);
    }
  });
  setBlueLetters([...blueLetters, ...newBlue]);
  setYellowLetters([...yellowLetters, ...newYellow]);
  setGrayLetters([...grayLetters, ...newGray]);

  setTimeout(() => {
    if (currentWord === word) {
      router.push(`/end?win=true&word=${word}&gameField=${JSON.stringify(rows)}`);
    } else if (curRow + 1 >= rows.length) {
      router.push(`/end?win=false&word=${word}&gameField=${JSON.stringify(rows)}`);
    }
  }, 1500);
  setCurRow(curRow + 1);
  setCurCol(0);
};

// Evaluates each keyboard key pulsation
const addKey = (key: string) => {
  console.log('addKey', key);

  const newRows = [...rows.map((row) => [...row])];

  if (key === 'ENTER') {
    checkWord();
  } else if (key === 'BACKSPACE') {
    if (colStateRef.current === 0) {
      newRows[curRow][0] = '';
      setRows(newRows);
      return;
    }
    newRows[curRow][colStateRef.current - 1] = '';
    setCurCol(colStateRef.current - 1);
    setRows(newRows);
    return;
  } else if (colStateRef.current >= newRows[curRow].length) {
    // End of line
    return;
  } else {
    newRows[curRow][colStateRef.current] = key;
    setRows(newRows);
    setCurCol(colStateRef.current + 1);
  }
};

// Recieves the word by mic (Here's the problem)
const addWord = (word: string) => {
  const letters = word.split('');
  const newRows = [...rows.map((row) => [...row])];

  letters.forEach((letter, index) => {
    if (index < 5) {
      newRows[curRow][index] = letter;
    }
  });

  setRows(newRows);
  setCurCol(Math.min(letters.length, 5));
  setTimeout(()=>checkWord(),1000);
};

/* More code, not related to the game logic */

return (
        <View style={styles.container}>
            {keys.map((row, rowIndex) => (
                <View key={`row-${rowIndex}`} style={styles.row}>
                    {row.map((key, keyIndex) => (
                        <Pressable
                            key={`key=${key}`}
                            onPress={() => (key === MICROPHONE ? handleMicrophonePress() : onKeyPressed(key))}
                            style={({pressed}) => [
                                styles.key,
                                {
                                    width: keyWidth,
                                    height: keyHeight,
                                    backgroundColor: '#DDD',
                                },
                                isSpecialKey(key) && {width: keyWidth * 1.5},
                                {
                                    backgroundColor: blueLetters.includes(key)
                                        ? '#6ABDED'
                                        : yellowLetters.includes(key)
                                          ? '#FFE44D'
                                          : grayLetters.includes(key)
                                            ? '#808080'
                                            : key === MICROPHONE && isRecording
                                              ? '#FF4444'
                                              : '#DDD',
                                },
                                pressed && {backgroundColor: '#868686'},
                            ]}
                        >
                            <Text style={[styles.keyText, key === 'ENTER' && {fontSize: 12}, isInLetters(key) && {color: '#FFFFFF'}]}>
                                {isSpecialKey(key) ? (
                                    key === ENTER ? (
                                        'Enter'
                                    ) : (
                                        <Ionicons name="backspace-outline" size={24} color={'black'} />
                                    )
                                ) : key === MICROPHONE ? (
                                    <Ionicons name="mic-outline" size={24} color={isRecording ? 'white' : 'black'} />
                                ) : (
                                    key
                                )}
                            </Text>
                        </Pressable>
                    ))}
                </View>
            ))}
        </View>
    );
};

export default game.tsx;

/* Styling code */


OnScreenKeyboard.tsx

import {Platform, Pressable, StyleSheet, Text, useWindowDimensions, View} from 'react-native';
import React, {useState} from 'react';
import {Ionicons} from '@expo/vector-icons';
import Voice from '@react-native-voice/voice';

type OnScreenKeyboardProps = {
    onKeyPressed: (key: string) => void;
    onWordRecognized: (word: string) => void;
    blueLetters: string[];
    yellowLetters: string[];
    grayLetters: string[];
};

export const ENTER = 'ENTER';
export const BACKSPACE = 'BACKSPACE';
export const MICROPHONE = 'MICROPHONE';

const keys = [
    ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
    ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', MICROPHONE],
    [ENTER, 'z', 'x', 'c', 'v', 'b', 'n', 'm', BACKSPACE],
];

const OnScreenKeyboard = ({onKeyPressed, onWordRecognized, blueLetters, yellowLetters, grayLetters}: OnScreenKeyboardProps) => {
    const {width} = useWindowDimensions();
    const keyWidth = Platform.OS === 'web' ? 58 : (width - 60) / keys[0].length;
    const keyHeight = 55;
    const [isRecording, setIsRecording] = useState(false);

    const isSpecialKey = (key: string) => [ENTER, BACKSPACE].includes(key);
    const isInLetters = (key: string) => [...blueLetters, ...yellowLetters, ...grayLetters].includes(key);

    React.useEffect(() => {
        Voice.onSpeechResults = onSpeechResults;
        return () => {
            Voice.destroy().then(Voice.removeAllListeners);
        };
    }, []);

    const removeAccents = (word: string) => {
        return word.replace(/á/g, 'a').replace(/é/g, 'e').replace(/í/g, 'i').replace(/ó/g, 'o').replace(/ú/g, 'u');
    };

    const onSpeechResults = (e: any) => {
        if (e.value && e.value[0]) {
            let word = e.value[0].toLowerCase().trim();
            word = removeAccents(word);

            if (word.length === 5) {
                onWordRecognized(word);
            }
        }
        setIsRecording(false);
    };

    const handleMicrophonePress = async () => {
        try {
            if (isRecording) {
                await Voice.stop();
                setIsRecording(false);
            } else {
                setIsRecording(true);
                await Voice.start('es-ES');
            }
        } catch (error) {
            console.error(error);
            setIsRecording(false);
        }
    };

    return (
        <View style={styles.container}>
            {keys.map((row, rowIndex) => (
                <View key={`row-${rowIndex}`} style={styles.row}>
                    {row.map((key, keyIndex) => (
                        <Pressable
                            key={`key=${key}`}
                            onPress={() => (key === MICROPHONE ? handleMicrophonePress() : onKeyPressed(key))}
                            style={({pressed}) => [
                                styles.key,
                                {
                                    width: keyWidth,
                                    height: keyHeight,
                                    backgroundColor: '#DDD',
                                },
                                isSpecialKey(key) && {width: keyWidth * 1.5},
                                {
                                    backgroundColor: blueLetters.includes(key)
                                        ? '#6ABDED'
                                        : yellowLetters.includes(key)
                                          ? '#FFE44D'
                                          : grayLetters.includes(key)
                                            ? '#808080'
                                            : key === MICROPHONE && isRecording
                                              ? '#FF4444'
                                              : '#DDD',
                                },
                                pressed && {backgroundColor: '#868686'},
                            ]}
                        >
                            <Text style={[styles.keyText, key === 'ENTER' && {fontSize: 12}, isInLetters(key) && {color: '#FFFFFF'}]}>
                                {isSpecialKey(key) ? (
                                    key === ENTER ? (
                                        'Enter'
                                    ) : (
                                        <Ionicons name="backspace-outline" size={24} color={'black'} />
                                    )
                                ) : key === MICROPHONE ? (
                                    <Ionicons name="mic-outline" size={24} color={isRecording ? 'white' : 'black'} />
                                ) : (
                                    key
                                )}
                            </Text>
                        </Pressable>
                    ))}
                </View>
            ))}
        </View>
    );
};

export default OnScreenKeyboard;

/* Styling code */

这是玩游戏的错误的演示(顺便说一句,它是西班牙语),显然,预期的行为是第二个单词被插入到第二行。

我尝试了一些方法来解决这个问题。我还尝试将麦克风拾取的单词插入到

addKey()

 函数中,用 
.split()
 逐个字母地插入,但这也不起作用,这对我来说没有意义。

我认为主要问题与游戏如何处理

curRow

 状态有关,但我再次尝试修复它,但无法做到。

javascript reactjs typescript react-native expo
1个回答
0
投票
问题可能出在初始化行的方式上。

new Array(ROWS).fill(new Array(5).fill(''))

 通过这一行,数组中的每一行都引用相同的内部数组

请使用

Array.from({ length: ROWS }, () => Array(5).fill(''))

 来代替。

背景:

// Change this line const [rows, setRows] = useState<string[][]>(new Array(ROWS).fill(new Array(5).fill(''))); // To this line const [rows, setRows] = useState<string[][]>( Array.from({ length: ROWS }, () => Array(5).fill('')) );
    
© www.soinside.com 2019 - 2024. All rights reserved.