I actually managed to solve the puzzle the first time quickly with sheer luck, but on the second run I was stumped. So I followed the only reasonable course of action: let the computer solve the puzzle for me!
The puzzle is as follows: there is a square (4 rows, 4 columns) of gold bars that can be either in horizontal or vertical position. You can make one bar pivot, but when you do that all the bars in the same row and column pivot too! You need to get all bars to the vertical position for the door to open to reveal... Cthulhu itself!
So let us use Rust for this puzzle, and see if a brute force approach can work.
Let's first define a few types. A grid is a 4x4 array of booleans and a position is a tuple. We'll have positions ranging from (0,0) to (3,3). A grid is solved is all elements are true (so true means vertical position for a bar).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pub type Grid = [[bool;4];4]; | |
pub type Pos = (usize,usize); | |
fn is_solved(grid: Grid) -> bool { | |
grid.iter().all(|r| r.iter().all(|c| *c)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fn swap(grid: &Grid, pos: Pos) -> Grid { | |
let mut grid = grid.clone(); | |
for a in 0..4 { | |
grid[pos.0][a] = !grid[pos.0][a]; | |
} | |
for a in 0..4 { | |
grid[a][pos.1] = !grid[a][pos.1]; | |
} | |
grid[pos.0][pos.1] = !grid[pos.0][pos.1]; | |
grid | |
} |
The main solving function is as follows: we're given a grid and we return a list of positions to swap to solve the puzzle. If the grid is already solved we return an empty vec. Otherwise we keep two structures: a map of grids to a vector of positions (showing how we got to that particular grid from the start grid) and a list of grids to process. We use a Deque here so we can put new grids at the end but process grids from the start, to do a breadth-first traversal of all possible grids.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::collections::HashMap; | |
use std::collections::VecDeque; | |
pub fn solve(grid: Grid) -> Vec<Pos> { | |
if is_solved(grid){ | |
return vec!(); | |
} | |
let mut ret = None; | |
let mut seen=HashMap::new(); | |
seen.insert(grid,Vec::new()); | |
let mut todo=VecDeque::new(); | |
todo.push_back(grid); | |
while ret.is_none(){ | |
match todo.pop_front(){ | |
Some(g)=> { | |
ret = try_all(&g,&mut seen,&mut todo); | |
}, | |
None=>panic!("no more grids to do"), | |
} | |
} | |
ret.expect("no result!") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fn try_all(grid: &Grid, | |
seen: &mut HashMap<Grid,Vec<Pos>>, | |
todo: &mut VecDeque<Grid> | |
) -> Option<Vec<Pos>> { | |
let path= match seen.get(grid){ | |
Some(p) => p.clone(), | |
None => panic!("no path to grid!") | |
}; | |
for a in 0..4 { | |
for b in 0..4 { | |
let g =swap(&grid,(a,b)); | |
let mut path = path.clone(); | |
path.push((a,b)); | |
match seen.get(&g){ | |
Some(v) => if v.len()>path.len() { | |
seen.insert(g,path.clone()); | |
}, | |
None => { | |
seen.insert(g,path.clone()); | |
todo.push_back(g); | |
}, | |
} | |
if is_solved(g){ | |
return Some(path); | |
} | |
} | |
} | |
None | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pub fn parse_grid(grid_desc :&str) -> Grid { | |
let mut grid: Grid = [[false;4];4]; | |
grid_desc.chars().collect::<Vec<char>>().chunks(4). | |
enumerate().for_each(|(a,cs)| { | |
cs.iter().enumerate().for_each(|(b,c)| { | |
if *c == '1' { | |
grid[a][b]=true; | |
} | |
}) | |
}); | |
grid | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use lovecraft::{ | |
solve, | |
parse_grid, | |
}; | |
use std::env; | |
fn main() { | |
let args: Vec<String> = env::args().collect(); | |
let grid_desc = &args[1]; | |
let moves = solve(parse_grid(grid_desc)); | |
println!("{:?}",moves); | |
println!("{} moves",moves.len()); | |
} |
This Rust code is probably fairly naive in places, since I only have a couple of weeks of evenings learning it. I'm not too happy with the number of clone() calls I had to add to make the borrow checker happy, on top of the ones I thought I needed anyway. Maybe immutable structures would in fact be easier to use here! Of course this code could be made parallel but since it gave me my answer in a few seconds I didn't need to improve it further.
Happy Rust Hacking!
1 comment:
I believe I found a direct way: you can flip precisely the bit in the (i,j) position as follows: perform the swap operation on all the squares in the i-th row and in the j-th column (i.e. flip in terms of swaps is defined exactly as swap in terms of flips).
Then you can simply collect all swaps as above for all the bits in the starting position and you're done.
But we can do better because the swap operations commute (this is not obvious and I only checked by drawing a picture) and are involutive, so you can actually reduce multiplicities of every swap mod 2, giving a shortest path (which is likely what your path search found).
This also shows that 16 swaps always suffice. And I believe 16 are also required for the position where the whole board needs to be flipped.
Post a Comment