Garland Solver

A simple puzzle solver for Garland Assemble mini-game found in Christmas Town in Torn.com

Back to Homepage

Interactive Demo

Click on any puzzle piece to rotate it. Use the solve button to automatically solve the puzzle using the GarlandSolver library.

How It Works

The GarlandSolver uses a constraint satisfaction algorithm to:

  1. Analyze possible rotations for each piece
  2. Apply adjacency constraints between pieces
  3. Iteratively reduce possibilities
  4. Find a valid solution that connects all pieces

Note:

The execution time will be higher in Torn.com's live environment because of other javascript code and other resources also running on the page. So far in my testing, the maximum time taken in live environment is yet to exceed 700ms.

If the puzzle sample you are using is taking much longer, try increasing the argument in this.#createAdjsNreducePossibilities(3); in "solve" method in the lib's code to 4 or 5.


async solve() {

    return new Promise((resolve, reject) => {
        try {
            this.#createPossibleOptions();
            this.#createAdjsNreducePossibilities(3); // increase this to 4 or 5 if your puzzle sample is taking too long to solve.
            
            const solution = this.#generateCombinations();
            if (solution) {
                resolve(solution);
            } else {
                    
                reject("No solution found.");
            }
        } catch (err) {
            reject(err);

        }
    });
}
                

I have set it by default to 3 because in my testing I found that on increasing it after the 3rd time, it did not reduce the possible solutions dramatically.

Running it 3 times was enough to get possibilities from 2 million to less than 40 thousand. Running it more than that, did not contribute much in terms of performance.

Basic Usage

// Create solver instance with puzzle data
const solver = new GarlandSolver(puzzleData);

// Solve the puzzle
solver.solve()
    .then(solution => {
        console.log("Solution found!!");
        console.log(solution);
    })
    .catch(err => {
        console.error("Error solving:", err);
    });

Puzzle Input and Output

  1. Add a fetch intercept to the page and listen for fetch calls. (See Also: Network Listeners for Tampermonkey)
  2. Look for the call that is made when the Garland Assemble game is started. The response of this call in JSON form, is the input puzzleData of the GarlandSolver.js
  3. Upon successfully finding the solution, it returns modified grid data in the same format. You can use this modified 'solution grid data' in whatever way you wish to implement the visual cues for the user to solve the puzzle.
  4. What I do in my own implementation is that I color the "cells" that need to be rotated/clicked to become equivalent to the state of "solution grid". Example of which is given below:
                                
function calculateClicks(originalGrid, solutionGrid) {
    let a = 0;
    let b = 0;
    const array = [];
    for (let i = 0; i < 25; i++) {
        let clicks = 0;
        const orig_cell = originalGrid.tails[a][b];
        const sol_cell = solutionGrid.tails[a][b];

        if (orig_cell !== null && sol_cell !== null) {
            const img = orig_cell.imageName;
                if (!img.includes("cross")) {
                    const orig_rot = normaliseRotationValue(orig_cell.rotation, img);
                    const sol_rot = normaliseRotationValue(sol_cell.rotation, img);
                    if (orig_rot !== sol_rot) {
                        if (img.includes("straight")) {
                            clicks += 1;
                        } else if (img.includes("angle")) {
                            if (orig_rot > sol_rot) {
                                clicks += ((360 - orig_rot) / 90) + (sol_rot / 90)
                            } else {
                            clicks += (sol_rot - orig_rot) / 90;
                        }
                    }
                }
            }
        }
        if (clicks > 0) {
        array.push([a, b, clicks]);
    }
        b += 1;
        if (b === 5) {
            b = 0;
            a += 1;
        }     
    }
    return array;
 }

 function normaliseRotationValue(rotation, img) {
    let rot = rotation;

    if (rot >= 360) {
        while (rot >= 360) {
            rot -= 360;
        }
    }
    if (img.includes("straight")) {
        if (rot === 0 || rot === 180) {
            rot = 0;
        } else if (rot === 270 || rot === 90) {
            rot = 90;
        }
    } else if (img.includes("angle")) {
        if (rot === 0) {
            rot = 360;
        }
    }
    return rot;
 }
                                
                            

    // The calculateClicks function will return an array of arrays that looks like the one below.
    // It only contains cells that need to be rotated in order to reach the solution state. 
    //First 2 elements of each array represent the x and y coordinates of the cell.
    //The 3rd element of each array represents the number of clicks required to rotate the cell to its final/required state.
    [
        [
            0,
            3,
            3
        ],
        [
            0,
            4,
            1
        ],
        [
            1,
            0,
            1
        ],
        [
            1,
            2,
            1
        ],
        [
            1,
            4,
            3
        ],
        [
            2,
            0,
            3
        ],
        [
            3,
            0,
            1
        ],
        [
            3,
            2,
            3
        ],
        [
            3,
            3,
            1
        ],
        [
            4,
            0,
            3
        ]
    ]