Most complicated lock patterns

As seen in Dr Zye's excellent video: youtube.com/watch?v=…mj-6zm-z&index=3

Rules of the lock pattern:
- Can't use a dot more than once
- Use at least 4 dots
- Order matters
- No skipping

Definition of "most complicated":
- Use all 9 dots
- Use all 8 slopes

I guess I'm missing a few patterns because I found 274 but he's quoting 296 possible ones.

Log in to post a comment.

// LL 2021

const display = 0; // min=0 max=274 step=1

Canvas.setpenopacity(-1);
const turtle = new Turtle();

function walk(i) {
    if (i==0) {
        for (var j=1; j<=9; j++) {
            drawDot(j);
        }
        slow = !display;
        target = display ? (display-1) : (Math.random() * 120 | 0);
        return true;
    }
    
    if (i==1) {
        getSolutions();
        if (target >= solutions.length) return false;
        console.log(`target: ${target+1} - solution: ${solutions[target]} - count: ${solutions.length}`);
        return true;
    }
    
    if (solutions[target].length < 2) return false;

    drawLine(solutions[target][0], solutions[target][1]);
    solutions[target].shift();
    sleep(100);

    return true;
}

const dslopes = [ [1, 0], [0, 1], [1, -1], [1, 1], [-1, 2], [1, 2], [-2, 1], [2, 1] ];

function getSolutions() {
    solutions = [];

    const solver = new Solver();

    var attempts = 0;
    while (++attempts < 100000) {
        const pattern = solver.iterate();
        if (isGood(pattern)) {
        	solutions.push(pattern);
        }
        if (solver.current_dot > 8) break;
    }
    
    solutions.sort(function(a, b) {
        for (var i = 0, c = Math.min(a.length, b.length); i < c; i++) {
            if (a[i] != b[i]) return a[i] - b[i];
        }
        return a.length - b.length;
    });
}

class Solver {
    constructor(start_dot = 1, start_slopes = [], depth = 0) {
        this.current_dot = start_dot;
        this.start_slope_count = start_slopes.length;
        this.used_slopes = [...start_slopes];
        this.available_slopes = [];
        this.depth = depth;
        for (var j = 1; j <= 8; j++) {
			var included = false;
			for (var k=0; k<this.used_slopes.length && !included; k++) {
				included |= j == this.used_slopes[k];
				included |= -j == this.used_slopes[k];
			}

            if (!included) {
                this.available_slopes.push(j);
            }
        }
        this.findOptions();
        this.child_solver = undefined;
    }
    
    findOptions() {
        this.options = [];
        this.available_slopes.forEach(slope => {
            for (var flip = -1; flip <= 1; flip += 2) {
                this.used_slopes.push(slope * flip);
                const new_pattern = getPattern(this.current_dot, this.used_slopes);
                if (new_pattern.length > 0) this.options.push(slope * flip);
                this.used_slopes.pop();
            }
        });
    }
    
    iterate() {
        if (this.used_slopes.length == 8) {
            const slopes = this.used_slopes;
            this.used_slopes = [];
            return getPattern(this.current_dot, slopes);
        }

        if (this.child_solver !== undefined) {
            const pattern = this.child_solver.iterate();
            if (this.child_solver.child_solver === undefined && this.child_solver.options.length < 1) {
            	this.child_solver = undefined;
            }
            return pattern;
        }

        if (this.current_dot > 9) return [];
        if (this.options.length < 1) {
        	if (this.depth == 0) {
        		this.current_dot++;
        		this.findOptions();
        	}
        	return [];
        }

        const option = this.options.shift();
        
        const new_slopes = (this.used_slopes.length > 0) ? [...this.used_slopes, option] : [ option ];
        this.child_solver = new Solver(this.current_dot, new_slopes, this.depth + 1);
        return this.child_solver.iterate();
    }
}

function getPattern(start_dot, slopes) {
    const pattern = [ start_dot ];
    for (var j=0; j<slopes.length; j++) {
        var next_dot = nextDot(pattern[pattern.length-1], slopes[j], 1);
        if (next_dot < 0) return [];
        if (pattern.includes(next_dot)) {
        	next_dot = nextDot(pattern[pattern.length-1], slopes[j], 2);
        	if (next_dot < 0) return [];
       	}
        pattern.push(next_dot);
    }
    return pattern;
}

function isGood(pattern) {
	if (pattern.length != 9) return false;
    for (var i=0; i<9; i++) {
    	for (var j=i+1; j<9; j++) {
    		if (pattern[i] == pattern[j]) return false;
    	}
    }
    return true;
}

function nextDot(dot, slope_index, step) {
    const flip = (slope_index < 0) ? -1 : 1;
    const dx = dslopes[Math.abs(slope_index)-1][0] * flip * step;
    const dy = dslopes[Math.abs(slope_index)-1][1] * flip * step;
    const x = (dot-1) % 3, y = (dot-1) / 3 | 0;
    if (x+dx < 0 || x+dx > 2 || y+dy < 0 || y+dy > 2) return -1;
    return x + dx + (y + dy) * 3 + 1;
}

function drawDot(index) {
    const x = (-1 + ((index-1) % 3)) * 60, y = (-1 + ((index-1) / 3 | 0)) * 60;
    for (var r = 6; r > 0; r -= .15) turtle.jump(x, y - r), turtle.circle(r, 0, 50);
}

function drawLine(index0, index1) {
    const cx0 = (-1 + ((index0-1) % 3)) * 60, cy0 = (-1 + ((index0-1) / 3 | 0)) * 60;
    const cx1 = (-1 + ((index1-1) % 3)) * 60, cy1 = (-1 + ((index1-1) / 3 | 0)) * 60;
    const thickness = 1, steps = 60;
    for (var j = 0, a = 0; j < steps; j++, a += Math.PI * 2 / steps) {
        const x0 = cx0 + thickness * Math.cos(a), y0 = cy0 + thickness * Math.sin(a);
        const x1 = cx1 + thickness * Math.cos(a), y1 = cy1 + thickness * Math.sin(a);
        turtle.jump(x0, y0); turtle.goto(x1, y1);
    }
}

function sleep(ms) {
    if (!slow) return;
    const start = Date.now();
    while (Date.now()-start < ms);
}