
From Idea to Reality: Creating Beyond Words, A Fun and Competitive Word Game Built with React Native & Amplify
Beyond Words is a fun and challenging word game where players form words from a set of letters, unlock hints, and discover bonus words. With online tournaments powered by AWS Amplify, you can compete with friends and players worldwide. Built with React Native, it offers a smooth and engaging experience, featuring swipe-based input, animations, and cloud-saved progress. Test your vocabulary and strategy in this exciting word puzzle adventure!
Published Jan 28, 2025
Inspired by one of my favorite word games, Zen World, I wanted to create something similar—something I could enjoy playing whenever I tried to relax or take a break from a busy day. Since I was already familiar with the logic behind the game, I knew it would be straightforward conceptually (but not so easy to implement 😉). The core mechanic is simple: swipe the given letters to form valid English words.
When it came to choosing the right platform to build Beyond Words, I had two options—Unity and React Native. Initially, I considered using Unity, but it felt like overkill for a word game. On the other hand, I hesitated with React Native because it’s primarily a UI framework for building web, Android, and iOS applications, not typically used for game development.
However, after some research, I discovered how React Native handles the PanResponder event—a native event system that consolidates multiple touch gestures into a single, seamless interaction. This allows for smooth and intuitive swipe gestures, making it perfect for a word game like Beyond Words. At that moment, I knew React Native was the right choice for handling swipe input which is an important feature of the game and bringing my vision to life.
To make signing into Beyond Words as simple and user-friendly as possible, I decided to implement passwordless authentication using email-based sign-up and sign-in. This allows players to easily access their accounts without the need to remember or enter a password. All they need to do is enter their email, and a secure one-time code is sent to them for instant access.
To implement this system, I used AWS Amplify, which seamlessly handles the authentication process. This setup ensures that every player's game data is securely stored online while maintaining easy and convenient access without the need for passwords. By integrating passwordless authentication, I created a smoother, more secure sign-in process that benefits both new and returning players.
To implement this system, I used AWS Amplify, which seamlessly handles the authentication process. This setup ensures that every player's game data is securely stored online while maintaining easy and convenient access without the need for passwords. By integrating passwordless authentication, I created a smoother, more secure sign-in process that benefits both new and returning players.
Implementing the game logic for Beyond Words was a bit challenging, but I’m going to break down the key parts to explain how it works.
Let’s start with the performLevelOperation function. This function is responsible for setting up the game levels and handling how the game progresses. Here's how the logic works for different levels:
- Levels 1-5: The player needs to find exactly 4 words that match the target words chosen by the game. If a word is valid but not the target word, it gets saved as a bonus word with lower points. As the levels progress, more words will be required.
- Levels 6-10: The number of words to find increases to 5.
- Levels 11-15: Players will have to find 8 words.
- Levels 16-21: The challenge ramps up to 10 words.
- Levels above 21: The number of words required increases further to 14.
The game dynamically adjusts the challenge based on the player's progress through these levels.
Here’s how the
performLevelOperation
function is implemented:```
const performLevelOperation = (): void => {
// Operation for levels 1-5
if (level >= 1 && level <= 5) {
const randomLevel = Math.floor(Math.random() * allAnagrams.length);
console.log({ randomLevel });
const selectedCurrentAnagram = getRandomItems(allAnagrams[randomLevel], 4);
const allCurrentAnagram = allAnagrams[randomLevel];
const sortedCurrentAnagram = selectedCurrentAnagram.sort((a, b) => a.length - b.length);
const sortedAllCurrentAnagram = allCurrentAnagram.sort((a, b) => a.length - b.length);
setSortedAnagrams(sortedCurrentAnagram);
setAnagrams(allCurrentAnagram.sort((a, b) => a.length - b.length));
const wordWithAssignedId = assignIDsToWord(sortedAllCurrentAnagram[sortedAllCurrentAnagram.length - 1]);
setWord(wordWithAssignedId);
shuffleWord();
}
// Repeat the similar structure for levels 6-10, 11-15, and so on...
}
```
// Operation for levels 1-5
if (level >= 1 && level <= 5) {
const randomLevel = Math.floor(Math.random() * allAnagrams.length);
console.log({ randomLevel });
const selectedCurrentAnagram = getRandomItems(allAnagrams[randomLevel], 4);
const allCurrentAnagram = allAnagrams[randomLevel];
const sortedCurrentAnagram = selectedCurrentAnagram.sort((a, b) => a.length - b.length);
const sortedAllCurrentAnagram = allCurrentAnagram.sort((a, b) => a.length - b.length);
setSortedAnagrams(sortedCurrentAnagram);
setAnagrams(allCurrentAnagram.sort((a, b) => a.length - b.length));
const wordWithAssignedId = assignIDsToWord(sortedAllCurrentAnagram[sortedAllCurrentAnagram.length - 1]);
setWord(wordWithAssignedId);
shuffleWord();
}
// Repeat the similar structure for levels 6-10, 11-15, and so on...
}
```
- The game chooses a random anagram set from an array of all possible anagrams for each level.
- The number of required words increases as the player progresses through levels, ensuring the game remains challenging.
- It sorts the anagram words by length to structure the gameplay.
- A word is selected and assigned an ID, which is used to manage and track the word in the game.
This logic will evolve as I continue to gather inspiration and player feedback, but it sets the foundation for the game's challenge and progression mechanics.
The
checkWord
function in Beyond Words handles word validation and checks whether a player has already found the word, or if the word is valid but not the target word. Here's an overview of how the logic works:- Forming the Word: The word formed by the player is either passed as an argument (
wordToUnlock
) or derived from the player's selected letters. The letters are then joined together to form the word, and it's converted to uppercase for uniformity. - Checking for Duplicates: The function first checks if the word has already been found by comparing it with the words stored in the
foundWords
array. If the word exists, the function updates the list of found words and exits. - Checking Bonus Words: If the word is valid but not the target word, it's checked against the
bonusWords
array. If the word is already a bonus word, the game will inform the player, but no further action will be taken. - Target Word Found: If the word is part of the anagram (i.e., it's a valid target word), the game will:
- Animate the word’s movement on the screen.
- Award the player coins based on the length of the word.
- Add the word to the list of
foundWords
and mark it as not a duplicate.
- Bonus Word: If the word is valid but not part of the anagram, it's added to the
bonusWords
list, and the player is informed that it's a valid word, but not the one they were looking for. Bonus words are rewarded with fewer coins compared to the target words.
Here's the implementation of the logic:
```JavaScript
const checkWord = (wordToUnlock?: string) => {
const joinedWord = wordToUnlock || Array.from(selectedLetterSequence)
.map((letter) => letter.char.toUpperCase())
.join("");
const wordExists = foundWords.some((fw) => fw.word === joinedWord);
// Check if the word has already been found
if (wordExists) {
const updatedWords = checkDuplicate(joinedWord);
console.log("You have found this word already");
return setFoundWords(updatedWords);
}
const joinedWord = wordToUnlock || Array.from(selectedLetterSequence)
.map((letter) => letter.char.toUpperCase())
.join("");
const wordExists = foundWords.some((fw) => fw.word === joinedWord);
// Check if the word has already been found
if (wordExists) {
const updatedWords = checkDuplicate(joinedWord);
console.log("You have found this word already");
return setFoundWords(updatedWords);
}
// Check if the word is in the bonus words list
if (bonusWords.includes(joinedWord.toUpperCase())) {
return console.log("You have found this VALID word already!!");
}
if (bonusWords.includes(joinedWord.toUpperCase())) {
return console.log("You have found this VALID word already!!");
}
// Check if the word is part of the anagram (target word)
if (sortedAnagrams.includes(joinedWord.toUpperCase())) {
const index = sortedAnagrams.findIndex((word) => word === joinedWord);
setAnimatedWord([wordPositions[index].word]);
moveText(wordPositions[index].word.length, wordPositions[index].x, wordPositions[index].y);
setCoins(coins + joinedWord.length * 50);
return setFoundWords([
...foundWords,
{ word: joinedWord, animate: true, isDuplicate: false },
]);
}
if (sortedAnagrams.includes(joinedWord.toUpperCase())) {
const index = sortedAnagrams.findIndex((word) => word === joinedWord);
setAnimatedWord([wordPositions[index].word]);
moveText(wordPositions[index].word.length, wordPositions[index].x, wordPositions[index].y);
setCoins(coins + joinedWord.length * 50);
return setFoundWords([
...foundWords,
{ word: joinedWord, animate: true, isDuplicate: false },
]);
}
// Check if the word is valid but not the target (bonus word)
if (!sortedAnagrams.includes(joinedWord.toUpperCase()) && anagrams.includes(joinedWord.toUpperCase())) {
setBonusWords([...bonusWords, joinedWord]);
console.log({ bonusWordPositionX, bonusWordPositionY });
setIsMovingBonusWord(true);
setAnimatedWord([joinedWord]);
moveText(4, bonusWordPositionX - moderateScale(43), bonusWordPositionY + moderateScale(420));
setCoins(coins + joinedWord.length * 10);
return console.log("It's a valid word, but not the word I'm looking for");
}
};
```
if (!sortedAnagrams.includes(joinedWord.toUpperCase()) && anagrams.includes(joinedWord.toUpperCase())) {
setBonusWords([...bonusWords, joinedWord]);
console.log({ bonusWordPositionX, bonusWordPositionY });
setIsMovingBonusWord(true);
setAnimatedWord([joinedWord]);
moveText(4, bonusWordPositionX - moderateScale(43), bonusWordPositionY + moderateScale(420));
setCoins(coins + joinedWord.length * 10);
return console.log("It's a valid word, but not the word I'm looking for");
}
};
```
- Duplicate Checking: Prevents players from submitting the same word multiple times.
- Bonus Words: Adds words to the bonus list if they are valid but not part of the target anagram, rewarding the player with fewer coins.
- Anagram Validation: Validates the word against the target anagram and rewards the player for correctly solving it.
- Word Animation: Words are animated when found, providing a more engaging user experience.
This logic makes the game feel dynamic and provides clear feedback to players as they progress. It also encourages them to explore more valid words beyond the target ones to earn bonus rewards.
The
shuffleWord
function is used to randomly shuffle the letters given to the player to form words. This is an important part of the game's dynamic gameplay as it presents a fresh challenge each time the letters are shuffled. Here's how the logic works:- Shuffling the Letters:
- The
setWord
function is used to update the word displayed to the player. - The previous word is cloned into a new array (
shuffledWord
), and then the Fisher-Yates shuffle algorithm is applied to randomly shuffle the letters of the word. - The Fisher-Yates algorithm works by iterating through the array in reverse order, and for each element, it picks a random index from the elements before it and swaps them.
- Ensuring Unique Words:
- The
getUniqueWords
function is used to compare two arrays of words and find words that are present in one array but not the other. - It converts both arrays (
arr1
andarr2
) into sets and filters out words that exist in both arrays. This ensures that only unique words from each array are returned.
Here is the code for both functions:
```
// Shuffle the letters of the word to form a new word
const shuffleWord = () => {
setWord((prevWord) => {
const shuffledWord = [...prevWord]; // Clone the current word into a new array
for (let i = shuffledWord.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // Get a random index
[shuffledWord[i], shuffledWord[j]] = [shuffledWord[j], shuffledWord[i]]; // Swap the elements
}
return shuffledWord; // Return the shuffled word
});
};
const shuffleWord = () => {
setWord((prevWord) => {
const shuffledWord = [...prevWord]; // Clone the current word into a new array
for (let i = shuffledWord.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // Get a random index
[shuffledWord[i], shuffledWord[j]] = [shuffledWord[j], shuffledWord[i]]; // Swap the elements
}
return shuffledWord; // Return the shuffled word
});
};
// Function to find unique words from two arrays
const getUniqueWords = (arr1: string[], arr2: string[]): string[] => {
const set1 = new Set(arr1); // Convert the first array to a set
const set2 = new Set(arr2); // Convert the second array to a set
const getUniqueWords = (arr1: string[], arr2: string[]): string[] => {
const set1 = new Set(arr1); // Convert the first array to a set
const set2 = new Set(arr2); // Convert the second array to a set
// Return the words that are in arr1 but not in arr2, and in arr2 but not in arr1
return [
...arr1.filter(word => !set2.has(word)), // Words in arr1 but not in arr2
...arr2.filter(word => !set1.has(word)), // Words in arr2 but not in arr1
];
};
```
return [
...arr1.filter(word => !set2.has(word)), // Words in arr1 but not in arr2
...arr2.filter(word => !set1.has(word)), // Words in arr2 but not in arr1
];
};
```
- shuffleWord:
- Randomly shuffles the letters in the
prevWord
array and updates theword
state with the shuffled result. - The Fisher-Yates shuffle is an efficient algorithm to ensure randomness with minimal computational overhead.
- Dynamic Gameplay: Shuffling the word offers a fresh experience for the player each time they try to form a valid word.
This logic keeps the game engaging and encourages players to discover new words while maintaining fairness in the gameplay.
The
unlockRandomLetter
function provides a mechanism for players to unlock a random letter from the remaining letters in a word, which helps them make progress when they're stuck. This feature adds a layer of strategic gameplay, where players can unlock clues for a price (although the price logic isn't implemented yet). Here’s how the function works:- Identifying Available Options:
- The function first defines an empty array
availableOptions
that will store possible options for unlocking a letter. - It then calls
getUniqueWords
to filter out words that have already been found by the player from the list of all available words (sortedAnagrams
). - It iterates through each word in the filtered list (
words
), and checks if each letter in the word has been unlocked yet. - The condition
!unlockedLetters.some(...)
ensures that only letters that haven’t been unlocked yet are considered for the unlocking options.
- Picking a Random Letter:
- If there are available letters to unlock (
availableOptions
is not empty), a random choice is selected usingMath.random()
. - This random choice represents a letter that the player can unlock to aid them in solving the word.
- Updating the State:
- Once a random letter is chosen, it is added to the
unlockedLetters
array (which keeps track of all unlocked letters) usingsetUnlockedLetters
.
- Edge Case - No Available Letters:
- If all letters in the word have already been unlocked, the function logs a message saying, "All letters are unlocked!" and exits.
Here’s the code:
```
const unlockRandomLetter = () => {
const availableOptions: { word: string; index: number }[] = [];
const availableOptions: { word: string; index: number }[] = [];
// Get the words that have not been found yet
const words = getUniqueWords(sortedAnagrams, foundWords.map(item => item.word));
// Iterate through words and find available letters to unlock
words.forEach((word) => {
for (let i = 0; i < word.length; i++) {
if (!unlockedLetters.some((entry) => entry.word === word && entry.index === i)) {
availableOptions.push({ word, index: i });
}
}
});
const words = getUniqueWords(sortedAnagrams, foundWords.map(item => item.word));
// Iterate through words and find available letters to unlock
words.forEach((word) => {
for (let i = 0; i < word.length; i++) {
if (!unlockedLetters.some((entry) => entry.word === word && entry.index === i)) {
availableOptions.push({ word, index: i });
}
}
});
// If there are no letters to unlock, log a message and return
if (availableOptions.length === 0) {
console.log("All letters are unlocked!");
return;
}
if (availableOptions.length === 0) {
console.log("All letters are unlocked!");
return;
}
// Pick a random available option (letter to unlock)
const randomChoice = availableOptions[Math.floor(Math.random() * availableOptions.length)];
const randomChoice = availableOptions[Math.floor(Math.random() * availableOptions.length)];
// Update the unlocked letters state
setUnlockedLetters([...unlockedLetters, randomChoice]);
};
```
setUnlockedLetters([...unlockedLetters, randomChoice]);
};
```
- Filtering Available Words:
- The function ensures that the letters from already found words are not available to unlock.
- Random Selection:
- By using
Math.random()
, the function selects a random letter to unlock, making each gameplay session unique.
- State Management:
- The
setUnlockedLetters
function updates the state, storing the letters that have been unlocked by the player.
- Price Implementation: You could implement a price for each letter unlock, deducting coins or other in-game currency when a letter is unlocked.
- UI Update: Visual cues for unlocked letters and the number of letters remaining could be added to the game interface, giving the player feedback.
The
unlockWord
function allows the player to unlock a complete word rather than just a single letter. This is especially useful when a player is struggling with an entire word and wants to gain a hint by unlocking a valid word from the set of possible anagrams. Here’s how the logic works:- Filtering Unfound Words:
- It calls the
getUniqueWords
function, similar to the letter unlocking logic, but in this case, it filters out words that have already been found. - This ensures the list of
words
contains only those that still need to be unlocked by the player.
- Selecting a Random Word:
- Once the list of available words is filtered, the function randomly selects a word from that list using
Math.random()
. - This randomly selected word is logged to the console for debugging or visibility purposes.
- Validating and Unlocking the Word:
- The selected word is passed into the
checkWord
function, which is responsible for validating the word. - The
checkWord
function checks if the word is valid, adds it to the found words list, or places it in the bonus words list if applicable.
- Return the Unlocked Word:
- The function returns the unlocked word, providing feedback on the word that was unlocked.
Here’s the code for the
unlockWord
function:```
const unlockWord = () => {
// Get unique words that haven't been found yet
const words = getUniqueWords(sortedAnagrams, foundWords.map(item => item.word));
// If there are no words left to unlock, return null
if (words.length === 0) return null;
// Log the random word to be unlocked (for visibility)
console.log(words[Math.floor(Math.random() * words.length)]);
// Check if the selected word is valid
checkWord(words[Math.floor(Math.random() * words.length)]);
// Return the word that was unlocked
return words[Math.floor(Math.random() * words.length)];
}
```
// Get unique words that haven't been found yet
const words = getUniqueWords(sortedAnagrams, foundWords.map(item => item.word));
// If there are no words left to unlock, return null
if (words.length === 0) return null;
// Log the random word to be unlocked (for visibility)
console.log(words[Math.floor(Math.random() * words.length)]);
// Check if the selected word is valid
checkWord(words[Math.floor(Math.random() * words.length)]);
// Return the word that was unlocked
return words[Math.floor(Math.random() * words.length)];
}
```
- Unlocking a Full Word:
- This function lets players unlock a complete word from the list of available words that they haven't yet discovered. It's an efficient way to help players move forward if they're stuck.
- Random Selection:
- Like the letter unlock mechanism, the word unlock mechanism is randomized, providing a variety of gameplay experiences each time.
- Game State Update:
- Once a word is unlocked, it is passed through the
checkWord
function, which handles adding the word to the list of found words or the bonus word list, and applying any relevant animations or score updates.
- Price Implementation: Like the
unlockRandomLetter
function, you can add a price system to unlock full words, making it a strategic choice for players to unlock hints as needed.
In Beyond Words, the game data is fetched and loaded from a backend service, ensuring that user progress is saved and retrieved whenever they re-enter the game. Below is the implementation of how game data is fetched by the user ID and then loaded into the application state.
The
fetchGameDataByUserId
function handles fetching the user's game data. It uses a client (generated via generateClient<Schema>
) to interact with the backend service and query game data specific to the logged-in user.- Getting the Current User: The
getCurrentUser()
function fetches theuserId
of the currently logged-in user. - Fetching Game Data: Using the
userId
, the function queries theGameData
model, filtering byuserId
, and returns a list of game records. Only one record per user is expected (limit: 1
). - Handling Errors: If there are any errors during the data fetch, they are logged to the console, and a warning message is displayed to the user.
- Handling Empty or Missing Data: If no data is found for the user, the
createGameData()
function is called to initialize a new game data record. - Updating Application State: Once the game data is successfully fetched, it is parsed and saved into the React state using
setLevel
,setCoins
,setFoundWords
, etc.
```
const fetchGameDataByUserId = async () => {
const { userId } = await getCurrentUser();
const client = generateClient<Schema>();
const { userId } = await getCurrentUser();
const client = generateClient<Schema>();
try {
// Fetch game data using list and filter by userId
const { data: gameDataList, errors } = await client.models.GameData.list({
filter: { userId: { eq: userId } },
limit: 1, // Assuming one record per user
authMode: 'userPool'
});
// Fetch game data using list and filter by userId
const { data: gameDataList, errors } = await client.models.GameData.list({
filter: { userId: { eq: userId } },
limit: 1, // Assuming one record per user
authMode: 'userPool'
});
if (errors) {
console.error(`Error fetching GameData: ${errors.map((e) => e.message).join(", ")}`);
toast.show(`Error fetching GameData: ${errors.map((e) => e.message).join(", ")}`, {
type: "warning",
placement: "bottom",
duration: 4000,
});
throw new Error("Failed to fetch GameData.");
}
console.error(`Error fetching GameData: ${errors.map((e) => e.message).join(", ")}`);
toast.show(`Error fetching GameData: ${errors.map((e) => e.message).join(", ")}`, {
type: "warning",
placement: "bottom",
duration: 4000,
});
throw new Error("Failed to fetch GameData.");
}
if (!gameDataList || !gameDataList.length) {
await createGameData();
return;
}
await createGameData();
return;
}
console.log({ gameDataList: gameDataList.length });
const gameData = gameDataList[0]; // Get the first record
const gameData = gameDataList[0]; // Get the first record
// Load game data into state
setLevel(gameData.level!);
setCoins(gameData.coins!);
setFoundWords(JSON.parse(gameData.foundWords!));
setBonusWords(JSON.parse(gameData.bonusWords!));
setSortedAnagrams(JSON.parse(gameData.sortedAnagrams!));
setWord(JSON.parse(gameData.word!));
setAnagrams(JSON.parse(gameData.anagrams!));
setUnlockedLetters(JSON.parse(gameData.unlockedLetters!));
setLevel(gameData.level!);
setCoins(gameData.coins!);
setFoundWords(JSON.parse(gameData.foundWords!));
setBonusWords(JSON.parse(gameData.bonusWords!));
setSortedAnagrams(JSON.parse(gameData.sortedAnagrams!));
setWord(JSON.parse(gameData.word!));
setAnagrams(JSON.parse(gameData.anagrams!));
setUnlockedLetters(JSON.parse(gameData.unlockedLetters!));
return gameData; // Return the fetched game data
} catch (error) {
console.error("Error in fetchGameDataByUserId:", error);
setIsLoading(false);
toast.show("Error in fetchGameDataByUserId: " + error, {
type: "warning",
placement: "bottom",
duration: 4000,
});
throw error;
}
};
```
} catch (error) {
console.error("Error in fetchGameDataByUserId:", error);
setIsLoading(false);
toast.show("Error in fetchGameDataByUserId: " + error, {
type: "warning",
placement: "bottom",
duration: 4000,
});
throw error;
}
};
```
The
fetchData
function, wrapped inside a useEffect
, is called when the component mounts (i.e., on the first render). The data is fetched asynchronously, and once the fetch is complete, the loading state is set to false
.```
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
await fetchGameDataByUserId();
setIsLoading(false);
};
const fetchData = async () => {
setIsLoading(true);
await fetchGameDataByUserId();
setIsLoading(false);
};
fetchData();
}, []);
```
}, []);
```
- State Management: The state variables such as
level
,coins
,foundWords
, etc., are updated after fetching the game data, ensuring that the user’s progress is reflected in the UI. - Error Handling: Any errors in fetching data are handled gracefully by displaying an
ActivityIndicator
and logging them to the console. - Loading State: The loading state (
setIsLoading
) helps show a loading spinner or indicator to the user while data is being fetched.
In Beyond Words, the tournament feature is what truly sets the game apart. Unlike most word games, where you’re either playing solo or against a random opponent, Beyond Words lets you directly compete against other players. What makes this even more exciting is the ability to challenge up to 5 players at once (this might evolve with future updates!). This creates a much more dynamic and competitive environment, offering a fresh and thrilling experience.
In these tournaments, players can showcase their word skills while competing in real-time. The fast-paced, multiplayer nature adds an exciting layer of strategy and engagement, something that’s relatively rare in the world of word games. Players can truly test themselves against multiple opponents simultaneously, which is why this feature makes Beyond Words stand out in the genre.
To see the tournament feature in action, I’ve added a YouTube link to the blog. Watching the gameplay in real time will give you a firsthand look at how the tournament works and how players compete against each other. It’s a great way to experience the excitement and energy of direct competition in Beyond Words.
In this blog post, I’ve covered a lot of exciting details about Beyond Words, especially the tournament feature, which really makes the game stand out. Unlike most word games, Beyond Words lets players compete directly against each other, up to five players at once (though that number might change!).
I also talked about how the game loads and saves data, so players can always pick up where they left off. This includes tracking things like progress, found words, and coins, making sure players’ data is updated and saved when they log back in.
With real-time score tracking, players can see their standing as the tournament progresses, which adds to the excitement and competitive nature of the game. Plus, the gameplay features like unlocking words, swiping letters, and bonus words keep things fun and engaging.
This is just the start for Beyond Words. I’ve got some awesome features already, but there’s a lot more coming—like high scores and other cool multiplayer modes—to keep players coming back for more.
Next up, I’ll be focusing on getting Beyond Words onto more platforms, including Android, iOS, and other devices that React Native supports. This will let players enjoy the game no matter what device they’re using. As I keep building and improving the game, there’s a lot to look forward to in terms of new features and making the game even bigger.
- YouTube: https://youtu.be/VJ51AvuKnEU