- Published on
Athoni II: Building the Word Search Game with Candy Crush Spice and a Sprinkle of LeetCode Challenges
- Authors
- Name
- Jack Nguyen
Table of Contents
I. Introduction + Demo

Let’s dive into the world of Word Search—our game that mashes together Candy Crush fun with brain-busting algorithms. The idea is simple: find meaningful words hidden in a grid (N x M, depending on the player’s level), score points, and enjoy special effects along the way! Plus, we’ve added an offline mode with endless levels. Here’s what you need to know:
Gameplay Basics:
- Each level has a set of mission words you need to find. Finish the list, and the game’s done for that stage! Bonus: You can rate your understanding of the words after.
- Points are earned based on the letters in each word. Special effects? They boost your score big time by clearing out multiple letters at once.
Features for Newbies:
- New players get 4 free tools to help: Double Score, Destroy 1 Cell, Hint, and Reset Board.
End-of-Level Bonuses:
- Finish a stage, and you’ll get random tools or an extra energy boost.
- Tools include:
- Double Score: Doubles the points for a word.
- Destroy 1 Cell: Removes a specific cell.
- Hint: Shows you where a word starts.
- Reset Board: Reshuffles the board for a fresh start.
Word Rules:
- Words can be horizontal, vertical, or a mix of both.
- They must be between 3 and 20 letters long.
Unlimited Fun:
- Play as many levels as you want, but each stage allows a max of 10 new words (by default).
- After finding 3 words, you’ll even get a fun pop-up to test your knowledge with a twist: some words will have missing letters for you to guess.
![]() | ![]() |
![]() | ![]() |
Let’s peel back the curtain on how we made this magic happen!
II. Collapsing Effects (Candy Crush Style) - a single block (Easy)
Video Demo:
Big shout-out to our tech lead, Nguyen Tran Minh Quang, and the dev Nguyen Dinh Quang Khanh, who made the grid collapse look amazing (with a tiny performance improvement from me, of course).
Please note that:
- To keep things simple, this guide only provides a high-level overview of the process.
- This guide focuses on a single block. When implementing a full solution, you'll need to consider multiple blocks. Identifying the blocks to replace or collapse is a task similar to a "hard" Leetcode problem.
Slides by Nguyen Tran Minh Quang
To make the collapsing effect animation easier to understand in React Native, here's how you can break it down using functional components and React Native animations:
Step 1: Set Up the Grid/Table

- Use a 2D array to represent the grid (NxM matrix). Each cell in the array is a block.
- Define the "selected block" (the block to be destroyed) by its position:
const grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
] // Example grid
const selectedBlock = { startX: 1, startY: 1, endX: 1, endY: 2 } // Range to destroy
- Render the grid using
FlatList
ormap
:

const renderGrid = grid.map((row, rowIndex) => (
<View key={rowIndex} style={{ flexDirection: 'row' }}>
{row.map((block, colIndex) => (
<Block key={`${rowIndex}-${colIndex}`} value={block} />
))}
</View>
))
Step 2: Delete the Block

- Highlight the block to be destroyed, then remove it after a short delay.
Functional Component for a Block:
const Block = ({ value, isSelected }) => {
const opacity = React.useRef(new Animated.Value(1)).current
React.useEffect(() => {
if (isSelected) {
Animated.timing(opacity, {
toValue: 0, // Fade out
duration: 300,
useNativeDriver: true,
}).start()
}
}, [isSelected])
return (
<Animated.View
style={{
width: 50,
height: 50,
backgroundColor: isSelected ? 'red' : 'blue',
opacity,
}}
>
<Text style={{ color: 'white', textAlign: 'center' }}>{value}</Text>
</Animated.View>
)
}
Step 3: Duplicate and Generate New Blocks Above
![]() | ![]() |
![]() | ![]() |
- Duplicate blocks above the destroyed area and generate new blocks to replace the deleted ones.
Logic for Grid Update:
const updateGrid = () => {
const newGrid = [...grid]
// Example: Replace the destroyed block with a new random block
for (let x = selectedBlock.startX; x <= selectedBlock.endX; x++) {
for (let y = selectedBlock.startY; y <= selectedBlock.endY; y++) {
newGrid[x][y] = Math.floor(Math.random() * 9) + 1 // Random new value
}
}
setGrid(newGrid)
}
Step 4: Animate the Collapse
![]() | ![]() |
![]() | ![]() |
- Move the blocks above the destroyed area down to fill the gap.
Animating the Falling Effect:
const FallingBlock = ({ value, isFalling }) => {
const translateY = React.useRef(new Animated.Value(-50)).current // Start above the grid
React.useEffect(() => {
if (isFalling) {
Animated.timing(translateY, {
toValue: 0, // Fall into place
duration: 500,
useNativeDriver: true,
}).start()
}
}, [isFalling])
return (
<Animated.View
style={{
transform: [{ translateY }],
width: 50,
height: 50,
backgroundColor: 'green',
}}
>
<Text style={{ color: 'white', textAlign: 'center' }}>{value}</Text>
</Animated.View>
)
}
Step 5: Update the Original Grid
- Replace the animated falling blocks with the updated grid layout.
State Management Example:
const [grid, setGrid] = React.useState(initialGrid)
const handleCollapse = () => {
// Trigger animations
setGrid((prevGrid) => {
const newGrid = [...prevGrid]
// Logic to update the grid
return newGrid
})
}
Optimize Performance for animation in React Native
- Use
useNativeDriver: true
for better performance. - Use
useState
anduseReducer
to handle complex grid updates. - For multi-block effects (like explosions), use
Animated.parallel
orAnimated.sequence
.
III. Algorithm Challenges (aka “LeetCode Nightmares”)
Note: Given simple constraints (small-sized table), our algorithm solutions here are not necessarily optimal.
3.1. Generating the Word Search Table (Medium)
Some additional requirements here:
- Tables must contain all the mission words. For smaller grids, the number of words is limited.
- Words can overlap, and the grid resets dynamically when words are found.
Shout out to Minh Tu who implemented this algorithm.
Step-by-Step
Define the Board Dimensions
- The board is represented as a 2D array with
n
rows andm
columns. - These dimensions are initialized in the constructor.
Pseudo Code:
create empty 2D array with dimensions (n x m)
- The board is represented as a 2D array with
Initialize the Board
- The board is initially filled with empty cells or placeholders (e.g.,
*
). - If there are predefined empty cells, these are marked to prevent words from being placed there.
Pseudo Code:
for each row in board: for each column in row: if cell is not in emptyCells: set cell to placeholder (e.g., `*`) else: leave cell empty
- The board is initially filled with empty cells or placeholders (e.g.,
Place Words on the Board
- A list of words (
this.words
) is used to populate the board. - For each word:
- Choose a random starting position (
startPos
) on the board. - Randomly decide its direction (horizontal, vertical, or diagonal).
- Ensure the word fits within the board boundaries and does not overlap with conflicting characters.
- Choose a random starting position (
Pseudo Code:
for each word in wordList: repeat until word is placed or maximum attempts reached: choose random start position (row, col) choose random direction (e.g., horizontal, vertical) check if word can be placed at (row, col) in direction if yes: place word in the grid store word position and direction
- A list of words (
Handle Overlapping Words
- Words can overlap if their letters match at intersecting positions.
- This is validated during the placement check, ensuring words blend naturally.
Pseudo Code:
for each character in word: if position already has a character: check if characters match if not: return false (cannot place word)
Fill Remaining Empty Cells
- Once all words are placed, any remaining empty cells are filled with random characters to complete the grid.
- This ensures no empty spots are visible to the player.
Pseudo Code:
for each cell in board: if cell is empty: set cell to random character
Return the Generated Board
- After populating the board, it is stored as the game’s current board state and returned for display or further processing.
Pseudo Code:
return populated 2D board array
Key Supporting Functions
randInt(a, b)
: Generates a random integer betweena
andb
. Used to pick random positions and directions.- Validation Logic: Ensures words do not exceed board boundaries and can overlap only where letters match.
3.2. Collapsing Effects - Identify Collapsing Blocks (Hard)
As I mentioned above, this guide Frontend only focuses on a single block. When implementing a full solution, you'll need to consider multiple blocks. Identifying the blocks to replace or collapse is a task of medium complexity, similar to a "hard" Leetcode problem.
Hint: run from the bottom to the top.
3.3. Searching for Words (Hard)
Hint: Trie + Backtracking + Hashmap to identify words in the grid.
Want practice? Solve this problem on LeetCode first—it’s the ultimate prep for implementing this feature: https://leetcode.com/problems/word-search-ii/
3.4. Other Advanced Features (Hard)
- Random Effects: Spice up the board with bonus cells (double score, explosions, etc.) using a simple Hashmap distribution:
{ normal: 0.9, double: 0.1, explosion: 0.1 }
. - Barriers: Add elements like rocks or walls to make it trickier.
- Special Effects: Explosions and score boosts require precise animations.
- Split Boards: Handle multiple grid areas as individual blocks that connect seamlessly.
That’s how we turned a simple Word Search game into a hybrid of Candy Crush and algorithmic wizardry. It’s not just a game—it’s a fun puzzle and a brain workout! Stay tuned for more updates (and maybe some hilarious tech stories along the way).