Two Approaches to Building a Letter Construction Game: Bitwise vs Array Patterns

While building my letter construction game WordGlyph, (https://wordglyph.xyz ), I explored two approaches to implement how players assemble letters using segments: bitwise operations and array patterns. Both are viable and interesting, but each comes with its tradeoffs. Here's an overview of both, how they work, and why I ultimately chose arrays for my game.

Bitwise Approach: Compact and Fast

In the bitwise method, each letter is represented as a 12-bit number, where each bit corresponds to a stick or segment position. This makes it compact (12 bits per letter) and efficient for operations like checking valid moves or completed letters.

How It Works:

Each letter is defined as a binary number. You can use bitwise operators to check if the current sticks plus a new stick match a letter.

function letterToGlyphBits(letter) {
    switch (letter.toUpperCase()) {
        case 'A': return parseInt("111010000000", 2); // Sticks: left vert, top horiz, mid horiz, right vert
        case 'B': return parseInt("110101001000", 2); // Sticks: left vert, top/bottom horiz, diagonals
        case 'H': return parseInt("101010000000", 2); // Left vert, mid horiz, right vert
        case 'X': return parseInt("000000000110", 2); // Diagonals
    }
}

function isValidMove(currentPattern, newStick, targetPattern) {
    return (newStick & targetPattern) && 
           ((currentPattern | newStick) === targetPattern);
}

// Example:
let currentPattern = parseInt("100010000000", 2); // Left + Right verticals
let newStick = parseInt("001000000000", 2);      // Middle horizontal
let H_PATTERN = parseInt("101010000000", 2);

if (isValidMove(currentPattern, newStick, H_PATTERN)) {
    console.log("Valid H completion!");
}

Pros:

  • Memory-efficient (12 bits per letter)
  • Fast operations using native bitwise logic
  • Elegant if you're comfortable with binary manipulations

Cons:

  • Not beginner-friendly; debugging binary logic can be tough
  • Limited to a fixed number of positions (e.g., 12 bits)
  • Harder to adapt and extend (e.g., adding more segments or positions)

Array Pattern Approach: Readable and Flexible

In the array method, each letter is represented as an array of segment IDs. This approach is more intuitive: you define each letter as a list of required segments, and validation checks involve iterating through arrays.

How It Works:

Each segment is assigned an ID, and letters are defined as arrays of IDs. Validation involves checking whether the current and new segments match a letter pattern.

const letterPatterns = {
    'A': [1, 2, 3, 5],    // Left vert, top horiz, right vert, mid horiz
    'B': [1, 2, 4, 7, 8], // Left vert, top/bottom horiz, diagonals
    'H': [1, 3, 5],       // Left vert, right vert, mid horiz
    'X': [8, 10],         // Diagonals
};

function isValidMove(currentSegments, newSegment) {
    return Object.values(letterPatterns).some(pattern => 
        currentSegments.every(seg => pattern.includes(seg)) && 
        pattern.includes(newSegment)
    );
}

// Example:
let currentSegments = [1, 3]; // Left and right verticals
let newSegment = 5;           // Middle horizontal

if (isValidMove(currentSegments, newSegment)) {
    console.log("Valid H completion!");
}

Pros:

  • Easy to read and debug. You're working with straightforward arrays
  • Flexible for adding or changing patterns
  • No restrictions on the number of segments or positions

Cons:

  • Higher memory usage compared to bitwise patterns
  • Validation involves iteration, which can be slower for large datasets (not an issue in most cases)

Why I Chose Arrays

Ultimately, I went with the array-based approach for the following reasons:

While the bitwise approach is elegant and efficient, it's harder to maintain and debug.