Published on

Athoni II: Building the Word Search Game with Candy Crush Spice and a Sprinkle of LeetCode Challenges

Authors
  • avatar
    Name
    Jack Nguyen
    Twitter
Table of Contents

I. Introduction + Demo

Cover Image

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:

  1. 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.
  2. Features for Newbies:

    • New players get 4 free tools to help: Double Score, Destroy 1 Cell, Hint, and Reset Board.
  3. 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.
  4. Word Rules:

    • Words can be horizontal, vertical, or a mix of both.
    • They must be between 3 and 20 letters long.
  5. 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.
CollapseCollapse
CollapseCollapse

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:

  1. To keep things simple, this guide only provides a high-level overview of the process.
  2. 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

Collapse
  • 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 or map:
Collapse
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

Collapse
  • 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

CollapseCollapse
CollapseCollapse
  • 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

CollapseCollapse
CollapseCollapse
  • 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

  1. Use useNativeDriver: true for better performance.
  2. Use useState and useReducer to handle complex grid updates.
  3. For multi-block effects (like explosions), use Animated.parallel or Animated.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

  1. Define the Board Dimensions

    • The board is represented as a 2D array with n rows and m columns.
    • These dimensions are initialized in the constructor.

    Pseudo Code:

    create empty 2D array with dimensions (n x m)
    

  1. 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
    

  1. 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.

    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
    

  1. 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)
    

  1. 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
    

  1. 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 between a and b. 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).