Cracker Barrel
August 19, 2014
While I was out of town last week, I ate dinner one evening at Cracker Barrel, which provides a triangle puzzle at each table so you can amuse yourself while you are waiting for your food. When my daughter challenged me to solve the problem, I failed, but I promised her that I would write a program to solve it.
As shown in the picture at right, the puzzle is a triangle with 15 holes and 14 pegs; one hole is initially vacant. The game is played by making 13 jumps; each jump removes one peg from the triangle, so at the end of 13 jumps there is one peg remaining. A jump takes a peg from a starting hole, over an occupied hole, to an empty finishing hole, removing the intermediate peg.
Your task is to write a program that solves the Cracker Barrel puzzle; find all possible solutions that start with a corner hole vacant and end with the remaining peg in the original vacant corner. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
That’s amusing. About a month and a half ago, I came across another site that had the same challenge, so I worked it out then:
Cracker Barrel Peg Game – framework and basic solver
Cracker Barrel Peg Game, Part 2 – which solutions are most solvable
Cracker Barrel Peg Game, Part 3 – interactive command line game
Although I will admit I don’t have anything that will end in a specific win state. It should be only a single line to add though.
(All three in Racket.)
I remember writing a solver for that puzzle long ago. I was still living with my parents, and I was writing in C, so that dates it around 1981-84. Of course, the code is long gone.
I think this is slightly slower – but slight more compact code…
In Python.
Recursive backtracking solution with memoization, using a bitset for the board. I was going to keep track of the moves in a tree structure, but thought it would be more fun to output a .dot file – see http://matthewarcus.files.wordpress.com/2014/08/puzzle.png for the (2Mb) result.
That should have been:
(Last minute “improvement” – means the same of course).
The solution that you posted above seems to timeout on my machine and on codepad. I also have a scheme solution that is timing out at the moment, attempting to debug it.
It is slow because it must form all possible solutions. It times out at both codepad and ideone, but runs to completion on my machine at work:
I didn’t think that you were wrong, was just curious. I was using the mit-scheme interpreter, which apparently has very low memory limits. Switch to Gauche the program ran fine.
My C++ program takes about 1.5ms to generate the .dot file (perf reckons about 2000000 cycles). As well as the obvious left-right symmetry, there’s also a forward and backward symmetry, swapping pegs for holes, which might save some time.
So I was having trouble with my scheme implemenation, it got to the point where I was just swapping in all of the functions that you wrote and I ran them and was still getting the incorrect answer. It turns out it was the way that I was encoding the data. If you run your program with data that marks the first peg as position 0 the results are skewed. Still haven’t figured out why.
Results:
40520
5736
((14) (12 13 14) (10 11 12) (5 8 11) (6 7 8) (11 12 13) (0 2 5) (1 3 6) (9 8 7) (5 4 3) (14 13 12) (6 3 1) (12 7 3) (3 1 0))
(define possible-moves
‘((0 1 3) (0 2 5)
(1 3 6) (1 4 8)
(2 4 7) (2 5 9)
(3 1 0) (3 4 5) (3 6 10) (3 7 12)
(4 7 11) (4 8 13)
(5 2 0) (5 4 3) (5 8 11) (5 9 14)
(6 3 1) (6 7 8)
(7 4 2) (7 8 9)
(8 4 1) (8 7 6)
(9 5 2) (9 8 7)
(10 6 3) (10 11 12)
(11 7 4) (11 12 13)
(12 7 3) (12 8 5) (12 11 10) (12 13 14)
(13 12 11) (13 8 4)
(14 9 5) (14 13 12)))
The initial board is also different due to the zero encoding:
(define init ‘((1 2 3 4 5 6 7 8 9 10 11 12 13 14)))
That (5 8 11) should be (5 8 12)
…………………………………………. That is what I get for drawing a shoddy triangle in my notes. Thank you.
Pleasure. Here’s some general code for the lower to higher moves:
Awesome, here is a solution using your standard prelude but with the data format a little more compressed:
So here is my code. It’s rough but it’s correct.
__author__ = ‘Christophe De Troyer’
# Define the the puzzle with 14 pegs and 1 hole.
# 0 = no peg, 1 = peg
INITIAL_BOARD = [0] + [1] * 14
# Define all possible moves.
moves = [
[0, 1, 3],
[0, 2, 5],
[1, 3, 6],
[1, 4, 8],
[2, 4, 7],
[2, 5, 9],
[3, 4, 5],
[3, 6, 10],
[3, 7, 12],
[4, 7, 11],
[4, 8, 13],
[5, 8, 12],
[5, 9, 14],
[6, 7, 8],
[7, 8, 9],
[10, 11, 12],
[11, 12, 13],
[12, 13, 14],
]
TOTALMOVES = []
global total_solutions
total_solutions = 0
global total_with_zero_peg
total_with_zero_peg = 0
for m in moves:
# Add initial move.
TOTALMOVES.append(m)
# Swap the move.
m2 = list(m)
temp = m2[0]
m2[0] = m2[2]
m2[2] = temp
TOTALMOVES.append(m2)
# A move can be made iff the right index in the list contains a False value in
# the list (puzzle).
def can_make_move(move, board):
if board[move[0]] == 1 and board[move[1]] == 1 and board[move[2]] \
== 0:
return True
return False
# A move is made by marking the index contained in the 2nd position of the move
# as true in the puzzle list and the first position as false.
def make_move(move, board):
new_board = list(board)
# Move the peg.
new_board[move[2]] = 1
new_board[move[0]] = 0
# Remove the peg that just got jumped over.
new_board[move[1]] = 0
return new_board
# A game is finished once the sum of the list is 1.
def is_finished(board):
return sum(board) == 1
# Returns a list of all the moves we can make in the current game state.
def yield_moves(board):
for move in TOTALMOVES:
if can_make_move(move, board):
yield move
def solve(board=INITIAL_BOARD, moves_done=[]):
# If the game is done, print out the moves.
global total_solutions, total_with_zero_peg
if is_finished(board):
total_solutions += 1
if board[0] == 1:
total_with_zero_peg += 1
else:
for move in yield_moves(board):
solve(make_move(move, board), moves_done + [m])
solve(INITIAL_BOARD, [])
print ‘Total solutions found: {0}:’.format(total_solutions)
print ‘Total solutions found with a peg in the zero hole: {0}’.format(total_with_zero_peg)
Another solution using Java Fork/Join framework:
Total solutions: 29760
Total solutions with peg in top hole: 6816
duration: 244
This is similar to the solver I wrote in the early 1980s.
I’d been sitting on this trying to get access to a fast server for some benchmarking. But I didn’t get the server, and it doesn’t matter. Suffice it to say that this solver runs in milliseconds.