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.
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.
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!");
}
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.
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!");
}
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.