-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 095743b
Showing
7 changed files
with
419 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
travis.yml |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Name | ||
|
||
[](https://travis-ci.org/petertseng-dp/rushhour) | ||
|
||
# Notes | ||
|
||
I used a similar solution as a Reddit poster - represent the board as a bitfield. | ||
The maximum supported board size is 6x6, since the borders are all set to 1 to make collision detection easier. | ||
Car orientation is set in the low-order bits (which are usually for the border). | ||
|
||
Confession: This was originally written in Ruby and then converted to Crystal. | ||
|
||
# Source | ||
|
||
https://www.reddit.com/r/dailyprogrammer/comments/56bh88 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
require "./src/rushhour" | ||
|
||
INPUTS = [ | ||
" | ||
..ABBC | ||
..A..C | ||
..ARRC> | ||
...EFF | ||
GHHE.. | ||
G..EII | ||
", | ||
].map(&.strip) | ||
|
||
INPUTS.each { |input| | ||
start = Time.now | ||
cars = parse_cars(input) | ||
soln = solve(cars).not_nil! | ||
puts input | ||
chunk_moves(soln).each_with_index { |(move, n), i| | ||
car, dir = move | ||
puts "%2d. %s %5s %d" % [i + 1, car, dir, n] | ||
} | ||
puts "Cars moved a total of #{soln.size} spaces" | ||
puts "Solved in #{Time.now - start}" | ||
puts | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
require 'set' | ||
|
||
# Include the edges in these numbers. | ||
# Constraint: With 64-bit integers, WIDTH * HEIGHT <= 64 | ||
WIDTH = 8 | ||
HEIGHT = 8 | ||
|
||
# Assumption: These squares are edges so it's OK to reuse these bits. | ||
VERTICAL = 1 | ||
HORIZONTAL = 2 | ||
|
||
CAR_ORIENTATION = VERTICAL | HORIZONTAL | ||
CAR_POSITION = -1 & ~CAR_ORIENTATION | ||
|
||
def square(row:, col:) | ||
1 << (row * HEIGHT + col) | ||
end | ||
|
||
ESCAPE = square(row: 3, col: WIDTH - 1) | ||
|
||
EDGE = (0...(WIDTH * HEIGHT)).select { |n| | ||
col = n % WIDTH | ||
row = n / WIDTH | ||
col == 0 || row == 0 || col == (WIDTH - 1) || row == (HEIGHT - 1) | ||
}.map { |n| 1 << n }.reduce(:|) & ~ESCAPE | ||
raise "bad edge #{EDGE.to_s(16)}" unless EDGE.to_s(16) == 'ff818181018181ff' | ||
|
||
ROWS = (0...HEIGHT).map { |row| (0...WIDTH).map { |col| square(row: row, col: col) }.reduce(:|) } | ||
COLS = (0...WIDTH).map { |col| (0...HEIGHT).map { |row| square(row: row, col: col) }.reduce(:|) } | ||
|
||
def parse_cars(grid) | ||
cars = Hash.new(0) | ||
grid.each_line.with_index { |line, row| | ||
line.chomp.each_char.with_index { |c, col| | ||
next if c == '>' || c == '.' | ||
cars[c] |= square(row: row + 1, col: col + 1) | ||
} | ||
} | ||
|
||
cars.each { |car, squares| | ||
horiz_car, vert_car = [ROWS, COLS].map { |lines| | ||
lines.count { |line| squares & line != 0 } == 1 | ||
} | ||
raise "#{car} can't be both vertical and horizontal" if horiz_car && vert_car | ||
raise "#{car} can't be neither vertical nor horizontal" if !horiz_car && !vert_car | ||
cars[car] |= HORIZONTAL if horiz_car | ||
cars[car] |= VERTICAL if vert_car | ||
} | ||
|
||
cars | ||
end | ||
|
||
def solve(initial_cars) | ||
# prev[current_board] = [cars, car, dir] | ||
prev = {initial_cars => nil} | ||
queue = [initial_cars] | ||
while (current_cars = queue.shift) | ||
# Could mask out the orientation, but it doesn't matter | ||
# because the orientation is an edge | ||
board = current_cars.values.reduce(EDGE, :|) | ||
current_cars.each { |car, squares_and_orientation| | ||
squares = squares_and_orientation & CAR_POSITION | ||
board_without_car = board & ~squares | ||
[ | ||
[HORIZONTAL, ->(s) { s << 1 }, :right], | ||
[HORIZONTAL, ->(s) { s >> 1 }, :left], | ||
[VERTICAL, ->(s) { s << WIDTH }, :down], | ||
[VERTICAL, ->(s) { s >> WIDTH }, :up], | ||
].each { |orientation, move_f, move_dir| | ||
next if squares_and_orientation & orientation == 0 | ||
new_position = move_f[squares] | ||
next unless board_without_car & new_position == 0 | ||
|
||
if new_position & ESCAPE != 0 | ||
trace_current_cars = current_cars | ||
moves = [[car, move_dir]] | ||
while (prev_move = prev[trace_current_cars]) | ||
prev_cars, prev_move_car, prev_move_dir = prev_move | ||
moves << [prev_move_car, prev_move_dir] | ||
trace_current_cars = prev_cars | ||
end | ||
return moves.reverse | ||
end | ||
|
||
# If cars ever have more than one orientation, it will be lost at the below line. | ||
new_cars = current_cars.merge(car => new_position | orientation) | ||
next if prev.has_key?(new_cars) | ||
queue << new_cars | ||
prev[new_cars] = [current_cars, car, move_dir] | ||
} | ||
} | ||
end | ||
end | ||
|
||
def chunk_moves(moves) | ||
prev_move = nil | ||
moves.each_with_object([]) { |move, chunked| | ||
if move == prev_move | ||
chunked[-1][-1] += 1 | ||
else | ||
chunked << move + [1] | ||
prev_move = move | ||
end | ||
} | ||
end | ||
|
||
INPUTS = [' | ||
...... | ||
...... | ||
RR....> | ||
...... | ||
...... | ||
...... | ||
',' | ||
..A... | ||
..A... | ||
RRA...> | ||
...... | ||
...... | ||
...... | ||
',' | ||
GAA..Y | ||
G.V..Y | ||
RRV..Y> | ||
..VZZZ | ||
....B. | ||
WWW.B. | ||
',' | ||
.....Y | ||
.....Y | ||
...RRY> | ||
...ZZZ | ||
...... | ||
...WWW | ||
',' | ||
TTTAU. | ||
...AU. | ||
RR..UB> | ||
CDDFFB | ||
CEEG.H | ||
VVVG.H | ||
',' | ||
QQQWEU | ||
TYYWEU | ||
T.RREU> | ||
IIO... | ||
.PO.AA | ||
.PSSDD | ||
',' | ||
..ABBC | ||
..A..C | ||
..ARRC> | ||
...EFF | ||
GHHE.. | ||
G..EII | ||
'].map(&:strip) | ||
|
||
INPUTS.each { |input| | ||
start = Time.now | ||
cars = parse_cars(input) | ||
soln = solve(cars) | ||
puts input | ||
chunk_moves(soln).each_with_index { |(car, dir, n), i| | ||
puts '%2d. %s %5s %d' % [i + 1, car, dir, n] | ||
} | ||
puts "Cars moved a total of #{soln.size} spaces" | ||
puts "Solved in #{Time.now - start}" | ||
puts | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
require "spec" | ||
require "../src/rushhour" | ||
|
||
inputs = [ | ||
{ | ||
" | ||
...... | ||
...... | ||
RR....> | ||
...... | ||
...... | ||
...... | ||
", | ||
5, | ||
}, | ||
{ | ||
" | ||
..A... | ||
..A... | ||
RRA...> | ||
...... | ||
...... | ||
...... | ||
", | ||
8, | ||
}, | ||
{ | ||
# I originally cared about this case because my code would move Z, Y, W, Y | ||
# and I wanted to see if I could get it to move Z, W, Y instead. | ||
# It's the same number of single-square moves, but fewer cars move. | ||
# The number of single-square moves should still be the primary to optimise for. | ||
# The number of distinct cars moved would have been the secondary. | ||
# I did not implement this secondary. | ||
" | ||
.....Y | ||
.....Y | ||
...RRY> | ||
...ZZZ | ||
...... | ||
...WWW | ||
", | ||
7, | ||
}, | ||
{ | ||
" | ||
GAA..Y | ||
G.V..Y | ||
RRV..Y> | ||
..VZZZ | ||
....B. | ||
WWW.B. | ||
", | ||
34, | ||
}, | ||
{ | ||
" | ||
TTTAU. | ||
...AU. | ||
RR..UB> | ||
CDDFFB | ||
CEEG.H | ||
VVVG.H | ||
", | ||
14, | ||
}, | ||
{ | ||
" | ||
QQQWEU | ||
TYYWEU | ||
T.RREU> | ||
IIO... | ||
.PO.AA | ||
.PSSDD | ||
", | ||
94, | ||
}, | ||
{ | ||
" | ||
..ABBC | ||
..A..C | ||
..ARRC> | ||
...EFF | ||
GHHE.. | ||
G..EII | ||
", | ||
84, | ||
}, | ||
] | ||
|
||
describe :solve do | ||
it "solves" do | ||
inputs.each { |board, moves| | ||
cars = parse_cars(board.strip) | ||
soln = solve(cars).not_nil! | ||
soln.size.should eq(moves) | ||
} | ||
end | ||
end |
Oops, something went wrong.