Last active
August 29, 2015 14:19
-
-
Save kierendavies/346727019458e89fe5ed to your computer and use it in GitHub Desktop.
Solver for the coloured-cubes puzzle that Caleb brought
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
# This program solves a puzzle I encountered recently. | |
# The puzzle consists of four cubes, each face of which has one of four colours. | |
# The objective is to arrange them in a column so that, looking down each side | |
# of the column, the four visible colours are all different. | |
require 'pp' | |
require 'set' | |
# A cube with colours attributed to its faces. | |
class Cube | |
# The face labels. | |
FACES = [:left, :right, :bottom, :top, :back, :front] | |
# The axis labels. | |
AXES = [:x, :y, :z] | |
# The face change mappings for rotations. | |
ROTATIONS = { | |
x: { | |
:bottom => :back, | |
:top => :front, | |
:back => :top, | |
:front => :bottom | |
}, | |
y: { | |
:left => :front, | |
:right => :back, | |
:back => :left, | |
:front => :right | |
}, | |
z: { | |
:left => :bottom, | |
:right => :top, | |
:bottom => :right, | |
:top => :left, | |
} | |
} | |
# The map specifying the colour on each face of the cube. | |
attr_reader :face_colours | |
# Create a new +Cube+ with the specified face colours in a +Hash+ or +Array+. | |
def initialize(face_colours) | |
if face_colours.is_a? Array | |
face_colours = Hash[FACES.zip(face_colours)] | |
end | |
@face_colours = face_colours | |
end | |
# Test if this +Cube+ is the same as another. | |
def eql?(other) | |
other.is_a?(Cube) && face_colours == other.face_colours | |
end | |
alias :== :eql? | |
# Compute a hash code for this object. | |
def hash | |
face_colours.hash | |
end | |
# Create a new +Cube+ which is equivalent to this one rotated 90 degrees about | |
# the specified axis, following the right-hand rule. | |
def rotate(axis) | |
Cube.new( | |
Hash[face_colours.map { |face, colour| | |
[ROTATIONS[axis][face] || face, colour] | |
}] | |
) | |
end | |
# Compute a +Set+ of all possible rotated versions of this cube. | |
def all_rotations | |
rotations = Set.new([self]) | |
last_rotations = rotations | |
4.times do # because octahedral symmetry group | |
next_rotations = Set.new | |
last_rotations.each do |cube| | |
AXES.each do |axis| | |
next_rotations << cube.rotate(axis) | |
end | |
end | |
rotations.merge(next_rotations) | |
last_rotations = next_rotations | |
end | |
return rotations | |
end | |
# Refer method calls of a face name to face colour lookups. | |
def method_missing(name) | |
if FACES.include? name | |
face_colours[name] | |
end | |
end | |
end | |
cubes = [ | |
Cube.new([:green, :green, :blue, :yellow, :red, :blue]), | |
Cube.new([:green, :yellow, :yellow, :yellow, :blue, :red]), | |
Cube.new([:red, :green, :green, :yellow, :blue, :red]), | |
Cube.new([:red, :green, :red, :yellow, :yellow, :blue]) | |
] | |
all_rotations = cubes.map(&:all_rotations).map(&:to_a) | |
solution = all_rotations.first.product(*all_rotations.drop(1)).find { |cubes| | |
[:left, :right, :bottom, :top].all? { |face| | |
cubes.map(&face).to_set.size == cubes.size | |
} | |
} | |
PP.pp solution |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment