I'm writing a Settlers of Catan clone for a class. One of the extra credit features is automatically determining which player has the longest road. I've thought about it, and it seems like some slight variation on depth-first search could work, but I'm having trouble figuring out what to do with cycle detection, how to handle the joining of a player's two initial road networks, and a few other minutiae. How could I do this algorithmically?
For those unfamiliar with the game, I'll try to describe the problem concisely and abstractly: I need to find the longest possible path in an undirected cyclic graph.
The Longest Road is a continuous road connecting two intersections, which consists of at least five individual road pieces and is not interrupted by game pieces belonging to other players. It has more individual road pieces than any other connecting road of this type.
You can break an opponent's road by building a settlement on an open intersection along his road! On page 8, in the example, Emily has the longest road card with 7 segments. She has two houses within those 7 segments. This was also answered directly in the online FAQ as pointed out by @shemmon.
Yes. Therefore, it must be 8, and your example must be 7. The rules indicate that you count the number of roads in the longest branch.
You lose longest road; by definition, none of you have a road with a length of at least 5. From the rules: Set the “Longest Road” card aside if—after a longest road is broken—several players tie for the new longest road or no one has a 5+ segment road.
This should be a rather easy algorithm to implement.
To begin with, separate out the roads into distinct sets, where all the road segments in each set are somehow connected. There's various methods on doing this, but here's one:
Note: As per the official Catan Rules, a road can be broken if another play builds a settlement on a joint between two segments. You need to detect this and not branch past the settlement.
Note, in the above, and following, steps, only consider the current players segments. You can ignore those other segments as though they weren't even on the map.
This gives you one or more sets, each containing one or more road segments.
Ok, for each set, do the following:
Now, from the segment you picked, do a recursive branching out depth-first search, keeping track of the length of the current road you've found so far. Always mark road segments as well, and don't branch into segments already marked. This will allow the algorithm to stop when it "eats its own tail".
Whenever you need to backtrack, because there are no more branches, take a note of the current length, and if it is longer than the "previous maximum", store the new length as the maximum.
Do this for all the sets, and you should have your longest road.
I would suggest a breadth-first search.
For each player:
Pick a starting node. Skip if it has zero or more than one connected neighbors and find all of the player's paths starting from it in a breadth-first manner. The catch: Once a node has been traversed to, it is "deactivated" for the all searches left in that turn. That is, it may no longer be traversed.
How can this be implemented? Here's one possible breadth-first algorithm that can be used:
After you do this once, move onto the next activated node and start the process all over again. Stop when all nodes are deactivated.
The maximum you have now is your longest road length, for the given player.
Note that this is slightly inefficient, but if performance doesn't matter, then this would work :)
IMPORTANT NOTE, thanks to Cameron MacFarland
Ruby pseudocode (assumes an get_neighbors
function for each node)
def explode n
exploded = n.get_neighbors # get all neighbors
exploded.select! { |i| i.activated? } # we only want activated ones
exploded.select! { |i| i.is_connected_to(n) } # we only want road-connected ones
return exploded
end
max = 0
nodes.each do |n| # for each node n
next if not n.activated? # skip if node is deactivated
if explode(n).empty? or explode(n).size > 1
n.deactivate # deactivate and skip if
next # there are no neighbors or
end # more than one
paths = [ [n] ] # start queue
until paths.empty? # do this until the queue is empty
curr_path = paths.pop # pop a path from the queue
exploded = explode(curr_path) # get all of the exploded valid paths
exploded.each { |i| i.deactivate } # deactivate all of the exploded valid points
if exploded.empty? # if no exploded paths
max = [max,curr_path.size].max # this is the end of the road, so compare its length to
# the max
else
exploded.each { |i| paths.unshift(curr_path.clone + i) }
# otherwise, add the new paths to the queue
end
end
end
puts max
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With